@defai.digital/automatosx 11.3.4 → 11.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/README.md +1 -1
- package/dist/index.js +2195 -193
- package/dist/mcp/index.js +2024 -84
- package/package.json +14 -14
package/dist/mcp/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path4 from 'path';
|
|
|
3
3
|
import path4__default, { dirname, join, extname as extname$1, basename, resolve, relative, isAbsolute, sep, parse, delimiter } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { mkdir, appendFile, readFile, readdir, writeFile, rename, unlink, copyFile, access, stat, realpath } from 'fs/promises';
|
|
6
|
-
import * as
|
|
6
|
+
import * as fs4 from 'fs';
|
|
7
7
|
import { existsSync, readFileSync, promises, mkdirSync, createWriteStream, writeFileSync, unlinkSync, constants } from 'fs';
|
|
8
8
|
import Database2 from 'better-sqlite3';
|
|
9
9
|
import { glob } from 'glob';
|
|
@@ -20,8 +20,9 @@ import { load } from 'js-yaml';
|
|
|
20
20
|
import { EventEmitter } from 'events';
|
|
21
21
|
import { findUp } from 'find-up';
|
|
22
22
|
import * as sqliteVec from 'sqlite-vec';
|
|
23
|
-
import { randomUUID } from 'crypto';
|
|
23
|
+
import { randomUUID, randomBytes, createHash } from 'crypto';
|
|
24
24
|
import yaml from 'yaml';
|
|
25
|
+
import { gzipSync, gunzipSync } from 'zlib';
|
|
25
26
|
|
|
26
27
|
var __defProp = Object.defineProperty;
|
|
27
28
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -318,16 +319,16 @@ var init_errors = __esm({
|
|
|
318
319
|
constructor(message, code = "E1001" /* CONFIG_INVALID */, suggestions = [], context) {
|
|
319
320
|
super(message, code, suggestions, context);
|
|
320
321
|
}
|
|
321
|
-
static notFound(
|
|
322
|
+
static notFound(path7) {
|
|
322
323
|
return new _ConfigError(
|
|
323
|
-
`Configuration file not found: ${
|
|
324
|
+
`Configuration file not found: ${path7}`,
|
|
324
325
|
"E1000" /* CONFIG_NOT_FOUND */,
|
|
325
326
|
[
|
|
326
327
|
'Run "automatosx setup" to create a new configuration',
|
|
327
328
|
"Specify a custom config path with --config option",
|
|
328
329
|
"Check that you are in a valid AutomatosX project directory"
|
|
329
330
|
],
|
|
330
|
-
{ path:
|
|
331
|
+
{ path: path7 }
|
|
331
332
|
);
|
|
332
333
|
}
|
|
333
334
|
static invalid(reason, context) {
|
|
@@ -342,7 +343,7 @@ var init_errors = __esm({
|
|
|
342
343
|
context
|
|
343
344
|
);
|
|
344
345
|
}
|
|
345
|
-
static parseError(error,
|
|
346
|
+
static parseError(error, path7) {
|
|
346
347
|
return new _ConfigError(
|
|
347
348
|
`Failed to parse configuration: ${error.message}`,
|
|
348
349
|
"E1002" /* CONFIG_PARSE_ERROR */,
|
|
@@ -351,7 +352,7 @@ var init_errors = __esm({
|
|
|
351
352
|
"Use a JSON validator to find syntax errors",
|
|
352
353
|
'Reset to default with "automatosx setup --force"'
|
|
353
354
|
],
|
|
354
|
-
{ path:
|
|
355
|
+
{ path: path7, originalError: error.message }
|
|
355
356
|
);
|
|
356
357
|
}
|
|
357
358
|
};
|
|
@@ -1164,11 +1165,11 @@ var init_workspace_indexer = __esm({
|
|
|
1164
1165
|
/**
|
|
1165
1166
|
* Detect file type and language
|
|
1166
1167
|
*/
|
|
1167
|
-
detectTypeAndLanguage(
|
|
1168
|
-
if (
|
|
1168
|
+
detectTypeAndLanguage(path7, ext) {
|
|
1169
|
+
if (path7.match(/package\.json|tsconfig\.json|\.config\.(js|ts|json|yaml|yml)|\.eslintrc|\.prettierrc/)) {
|
|
1169
1170
|
return { type: "config", language: "json" };
|
|
1170
1171
|
}
|
|
1171
|
-
if (
|
|
1172
|
+
if (path7.match(/\.(test|spec)\.(ts|js|tsx|jsx)$/) || path7.includes("__tests__")) {
|
|
1172
1173
|
const lang = LANGUAGE_MAP[ext];
|
|
1173
1174
|
return { type: "test", language: lang };
|
|
1174
1175
|
}
|
|
@@ -1185,15 +1186,15 @@ var init_workspace_indexer = __esm({
|
|
|
1185
1186
|
*
|
|
1186
1187
|
* Higher score = more likely to be relevant
|
|
1187
1188
|
*/
|
|
1188
|
-
calculateImportance(
|
|
1189
|
+
calculateImportance(path7, type, size) {
|
|
1189
1190
|
let score = 0.5;
|
|
1190
|
-
if (
|
|
1191
|
-
if (
|
|
1192
|
-
if (
|
|
1191
|
+
if (path7.match(/^(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 1;
|
|
1192
|
+
if (path7.match(/^src\/(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 0.95;
|
|
1193
|
+
if (path7.match(/^src\/cli\/(index|main)\.(ts|js)$/)) score = 0.9;
|
|
1193
1194
|
if (type === "config") score = 0.85;
|
|
1194
|
-
if (
|
|
1195
|
-
if (
|
|
1196
|
-
if (
|
|
1195
|
+
if (path7.includes("/core/")) score += 0.15;
|
|
1196
|
+
if (path7.includes("/types/")) score += 0.1;
|
|
1197
|
+
if (path7.includes("/utils/")) score += 0.05;
|
|
1197
1198
|
if (type === "test") score = Math.max(0.3, score - 0.3);
|
|
1198
1199
|
if (type === "doc") score = Math.max(0.4, score - 0.2);
|
|
1199
1200
|
if (size > 1e4) score += 0.05;
|
|
@@ -1669,10 +1670,10 @@ function findOnPathUnix(cmdBase) {
|
|
|
1669
1670
|
try {
|
|
1670
1671
|
const which = spawnSync("which", [cmdBase], { timeout: 3e3 });
|
|
1671
1672
|
if (which.status === 0) {
|
|
1672
|
-
const
|
|
1673
|
-
if (
|
|
1674
|
-
logger.debug("Found via which", { cmdBase, path:
|
|
1675
|
-
return { found: true, path:
|
|
1673
|
+
const path7 = which.stdout.toString().trim();
|
|
1674
|
+
if (path7) {
|
|
1675
|
+
logger.debug("Found via which", { cmdBase, path: path7 });
|
|
1676
|
+
return { found: true, path: path7 };
|
|
1676
1677
|
}
|
|
1677
1678
|
}
|
|
1678
1679
|
} catch (error) {
|
|
@@ -2800,7 +2801,7 @@ ${request.prompt}`;
|
|
|
2800
2801
|
}
|
|
2801
2802
|
if (process.env.AUTOMATOSX_DEBUG_PROMPT === "true") {
|
|
2802
2803
|
const debugPath = path4.join(process.cwd(), "automatosx/tmp/debug-prompt.txt");
|
|
2803
|
-
|
|
2804
|
+
fs4.writeFileSync(debugPath, `=== FULL PROMPT SENT TO ${this.getCLICommand()} ===
|
|
2804
2805
|
|
|
2805
2806
|
${fullPrompt}
|
|
2806
2807
|
|
|
@@ -4711,11 +4712,11 @@ var VALIDATION_LIMITS = {
|
|
|
4711
4712
|
MAX_PORT: 65535
|
|
4712
4713
|
// Maximum port number
|
|
4713
4714
|
};
|
|
4714
|
-
function isValidRelativePath(
|
|
4715
|
-
if (!
|
|
4715
|
+
function isValidRelativePath(path7) {
|
|
4716
|
+
if (!path7 || typeof path7 !== "string") {
|
|
4716
4717
|
return false;
|
|
4717
4718
|
}
|
|
4718
|
-
const normalizedPath =
|
|
4719
|
+
const normalizedPath = path7.replace(/\\/g, "/");
|
|
4719
4720
|
if (normalizedPath.startsWith("/")) {
|
|
4720
4721
|
return false;
|
|
4721
4722
|
}
|
|
@@ -5432,7 +5433,7 @@ var safeNameSchema = z.string().min(1).max(VALIDATION_LIMITS.MAX_NAME_LENGTH).re
|
|
|
5432
5433
|
"Name must be alphanumeric with dash/underscore only"
|
|
5433
5434
|
).describe("Safe identifier name");
|
|
5434
5435
|
var relativePathSchema = z.string().min(1).refine(
|
|
5435
|
-
(
|
|
5436
|
+
(path7) => !path7.includes("..") && !path7.startsWith("/"),
|
|
5436
5437
|
"Path must be relative (no ../, no absolute paths)"
|
|
5437
5438
|
).describe("Relative path within project");
|
|
5438
5439
|
var fileExtensionSchema = z.string().regex(
|
|
@@ -5772,16 +5773,16 @@ async function loadConfigUncached(projectDir) {
|
|
|
5772
5773
|
});
|
|
5773
5774
|
return config;
|
|
5774
5775
|
}
|
|
5775
|
-
async function loadConfigFile(
|
|
5776
|
+
async function loadConfigFile(path7) {
|
|
5776
5777
|
try {
|
|
5777
|
-
const content = await readFile(
|
|
5778
|
+
const content = await readFile(path7, "utf-8");
|
|
5778
5779
|
if (content.length > VALIDATION_LIMITS.MAX_CONFIG_FILE_SIZE) {
|
|
5779
5780
|
throw ConfigError.parseError(
|
|
5780
5781
|
new Error(`Config file too large (max ${VALIDATION_LIMITS.MAX_CONFIG_FILE_SIZE / 1024}KB, got ${Math.ceil(content.length / 1024)}KB)`),
|
|
5781
|
-
|
|
5782
|
+
path7
|
|
5782
5783
|
);
|
|
5783
5784
|
}
|
|
5784
|
-
const ext = extname(
|
|
5785
|
+
const ext = extname(path7).toLowerCase();
|
|
5785
5786
|
let userConfig;
|
|
5786
5787
|
try {
|
|
5787
5788
|
if (ext === ".yaml" || ext === ".yml") {
|
|
@@ -5790,7 +5791,7 @@ async function loadConfigFile(path6) {
|
|
|
5790
5791
|
userConfig = JSON.parse(content);
|
|
5791
5792
|
}
|
|
5792
5793
|
} catch (parseError) {
|
|
5793
|
-
throw ConfigError.parseError(parseError,
|
|
5794
|
+
throw ConfigError.parseError(parseError, path7);
|
|
5794
5795
|
}
|
|
5795
5796
|
const config = mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
5796
5797
|
if (config.execution && userConfig.execution?.maxConcurrentAgents === void 0) {
|
|
@@ -5806,35 +5807,35 @@ async function loadConfigFile(path6) {
|
|
|
5806
5807
|
if (validationErrors.length > 0) {
|
|
5807
5808
|
throw ConfigError.invalid(
|
|
5808
5809
|
validationErrors.join("; "),
|
|
5809
|
-
{ path:
|
|
5810
|
+
{ path: path7, errors: validationErrors }
|
|
5810
5811
|
);
|
|
5811
5812
|
}
|
|
5812
|
-
logger.info("Config loaded successfully", { path: normalizePath(
|
|
5813
|
+
logger.info("Config loaded successfully", { path: normalizePath(path7), format: ext });
|
|
5813
5814
|
return config;
|
|
5814
5815
|
} catch (error) {
|
|
5815
5816
|
if (error instanceof ConfigError) {
|
|
5816
5817
|
throw error;
|
|
5817
5818
|
}
|
|
5818
5819
|
if (error.code === "ENOENT") {
|
|
5819
|
-
throw ConfigError.notFound(
|
|
5820
|
+
throw ConfigError.notFound(path7);
|
|
5820
5821
|
}
|
|
5821
5822
|
if (error.code === "EACCES") {
|
|
5822
5823
|
throw new ConfigError(
|
|
5823
|
-
`Permission denied reading config: ${
|
|
5824
|
+
`Permission denied reading config: ${path7}`,
|
|
5824
5825
|
"E1002" /* CONFIG_PARSE_ERROR */,
|
|
5825
5826
|
[
|
|
5826
5827
|
"Check file permissions",
|
|
5827
5828
|
"Run with appropriate user privileges",
|
|
5828
5829
|
"Verify the file is accessible"
|
|
5829
5830
|
],
|
|
5830
|
-
{ path:
|
|
5831
|
+
{ path: path7, error: error.message }
|
|
5831
5832
|
);
|
|
5832
5833
|
}
|
|
5833
5834
|
throw new ConfigError(
|
|
5834
5835
|
`Failed to load config: ${error.message}`,
|
|
5835
5836
|
"E1002" /* CONFIG_PARSE_ERROR */,
|
|
5836
5837
|
["Check file format and permissions"],
|
|
5837
|
-
{ path:
|
|
5838
|
+
{ path: path7, originalError: error.message }
|
|
5838
5839
|
);
|
|
5839
5840
|
}
|
|
5840
5841
|
}
|
|
@@ -5850,8 +5851,8 @@ function validateConfigWithZod(config) {
|
|
|
5850
5851
|
return ["Configuration validation failed with unknown error structure"];
|
|
5851
5852
|
}
|
|
5852
5853
|
return result.error.issues.map((err) => {
|
|
5853
|
-
const
|
|
5854
|
-
return `${
|
|
5854
|
+
const path7 = err.path.join(".");
|
|
5855
|
+
return `${path7}: ${err.message}`;
|
|
5855
5856
|
});
|
|
5856
5857
|
}
|
|
5857
5858
|
function validateConfig(config) {
|
|
@@ -6261,8 +6262,8 @@ var PathError = class extends Error {
|
|
|
6261
6262
|
};
|
|
6262
6263
|
|
|
6263
6264
|
// src/shared/validation/path-resolver.ts
|
|
6264
|
-
function isWindowsPath(
|
|
6265
|
-
return /^[a-zA-Z]:[/\\]/.test(
|
|
6265
|
+
function isWindowsPath(path7) {
|
|
6266
|
+
return /^[a-zA-Z]:[/\\]/.test(path7);
|
|
6266
6267
|
}
|
|
6267
6268
|
async function detectProjectRoot(startDir = process.cwd()) {
|
|
6268
6269
|
if (process.env.AUTOMATOSX_PROJECT_ROOT) {
|
|
@@ -6362,8 +6363,8 @@ var PathResolver = class {
|
|
|
6362
6363
|
/**
|
|
6363
6364
|
* Validate path is within allowed base directory
|
|
6364
6365
|
*/
|
|
6365
|
-
validatePath(
|
|
6366
|
-
const normalized = normalizePath(resolvePath(
|
|
6366
|
+
validatePath(path7, baseDir) {
|
|
6367
|
+
const normalized = normalizePath(resolvePath(path7));
|
|
6367
6368
|
const base = normalizePath(resolvePath(baseDir));
|
|
6368
6369
|
const separator = "/";
|
|
6369
6370
|
const pathWithSep = normalized + separator;
|
|
@@ -6373,15 +6374,15 @@ var PathResolver = class {
|
|
|
6373
6374
|
/**
|
|
6374
6375
|
* Check if path is within allowed boundaries
|
|
6375
6376
|
*/
|
|
6376
|
-
isPathAllowed(
|
|
6377
|
-
const boundary = this.checkBoundaries(
|
|
6377
|
+
isPathAllowed(path7) {
|
|
6378
|
+
const boundary = this.checkBoundaries(path7);
|
|
6378
6379
|
return boundary === "agent_workspace" || boundary === "user_project";
|
|
6379
6380
|
}
|
|
6380
6381
|
/**
|
|
6381
6382
|
* Check which boundary a path belongs to
|
|
6382
6383
|
*/
|
|
6383
|
-
checkBoundaries(
|
|
6384
|
-
const normalized = resolvePath(
|
|
6384
|
+
checkBoundaries(path7) {
|
|
6385
|
+
const normalized = resolvePath(path7);
|
|
6385
6386
|
if (this.validatePath(normalized, this.config.agentWorkspace)) {
|
|
6386
6387
|
return "agent_workspace";
|
|
6387
6388
|
}
|
|
@@ -6399,15 +6400,15 @@ var PathResolver = class {
|
|
|
6399
6400
|
/**
|
|
6400
6401
|
* Get relative path from project root
|
|
6401
6402
|
*/
|
|
6402
|
-
getRelativeToProject(
|
|
6403
|
-
const normalized = resolvePath(
|
|
6403
|
+
getRelativeToProject(path7) {
|
|
6404
|
+
const normalized = resolvePath(path7);
|
|
6404
6405
|
return normalizePath(getRelativePath(this.config.projectDir, normalized));
|
|
6405
6406
|
}
|
|
6406
6407
|
/**
|
|
6407
6408
|
* Get relative path from working directory
|
|
6408
6409
|
*/
|
|
6409
|
-
getRelativeToWorking(
|
|
6410
|
-
const normalized = resolvePath(
|
|
6410
|
+
getRelativeToWorking(path7) {
|
|
6411
|
+
const normalized = resolvePath(path7);
|
|
6411
6412
|
return normalizePath(getRelativePath(this.config.workingDir, normalized));
|
|
6412
6413
|
}
|
|
6413
6414
|
/**
|
|
@@ -6426,11 +6427,11 @@ var PathResolver = class {
|
|
|
6426
6427
|
* Validate path is within project boundaries
|
|
6427
6428
|
* @throws PathError if outside project
|
|
6428
6429
|
*/
|
|
6429
|
-
validateInProject(
|
|
6430
|
-
const boundary = this.checkBoundaries(
|
|
6430
|
+
validateInProject(path7) {
|
|
6431
|
+
const boundary = this.checkBoundaries(path7);
|
|
6431
6432
|
if (boundary === "outside_boundaries" || boundary === "system_restricted") {
|
|
6432
6433
|
throw new PathError("Path outside project directory", {
|
|
6433
|
-
path:
|
|
6434
|
+
path: path7,
|
|
6434
6435
|
projectDir: this.config.projectDir,
|
|
6435
6436
|
boundary
|
|
6436
6437
|
});
|
|
@@ -8297,18 +8298,18 @@ var ProviderSessionManager = class {
|
|
|
8297
8298
|
};
|
|
8298
8299
|
var providerSessionInstances = /* @__PURE__ */ new Map();
|
|
8299
8300
|
async function getProviderSession(workspacePath) {
|
|
8300
|
-
let
|
|
8301
|
+
let path7;
|
|
8301
8302
|
{
|
|
8302
8303
|
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
8303
|
-
|
|
8304
|
+
path7 = process.cwd();
|
|
8304
8305
|
} else {
|
|
8305
|
-
|
|
8306
|
+
path7 = await detectProjectRoot();
|
|
8306
8307
|
}
|
|
8307
8308
|
}
|
|
8308
|
-
if (!providerSessionInstances.has(
|
|
8309
|
-
providerSessionInstances.set(
|
|
8309
|
+
if (!providerSessionInstances.has(path7)) {
|
|
8310
|
+
providerSessionInstances.set(path7, new ProviderSessionManager(path7));
|
|
8310
8311
|
}
|
|
8311
|
-
return providerSessionInstances.get(
|
|
8312
|
+
return providerSessionInstances.get(path7);
|
|
8312
8313
|
}
|
|
8313
8314
|
|
|
8314
8315
|
// src/core/router/router.ts
|
|
@@ -15242,9 +15243,9 @@ var DependencyGraphBuilder = class {
|
|
|
15242
15243
|
detectCycles(graph) {
|
|
15243
15244
|
const visiting = /* @__PURE__ */ new Set();
|
|
15244
15245
|
const visited = /* @__PURE__ */ new Set();
|
|
15245
|
-
const visit = (nodeName,
|
|
15246
|
+
const visit = (nodeName, path7) => {
|
|
15246
15247
|
if (visiting.has(nodeName)) {
|
|
15247
|
-
throw new Error(`Circular dependency detected: ${[...
|
|
15248
|
+
throw new Error(`Circular dependency detected: ${[...path7, nodeName].join(" \u2192 ")}`);
|
|
15248
15249
|
}
|
|
15249
15250
|
if (visited.has(nodeName)) {
|
|
15250
15251
|
return;
|
|
@@ -15259,7 +15260,7 @@ var DependencyGraphBuilder = class {
|
|
|
15259
15260
|
}
|
|
15260
15261
|
visiting.add(nodeName);
|
|
15261
15262
|
for (const dependency of node.dependencies) {
|
|
15262
|
-
visit(dependency, [...
|
|
15263
|
+
visit(dependency, [...path7, nodeName]);
|
|
15263
15264
|
}
|
|
15264
15265
|
visiting.delete(nodeName);
|
|
15265
15266
|
visited.add(nodeName);
|
|
@@ -16992,59 +16993,59 @@ var DANGEROUS_PATH_PATTERNS = [
|
|
|
16992
16993
|
// Common data drive (Windows, alt format)
|
|
16993
16994
|
];
|
|
16994
16995
|
var SUSPICIOUS_PATH_CHARS = /[<>:|"]/;
|
|
16995
|
-
function validatePathParameter(
|
|
16996
|
-
if (!
|
|
16996
|
+
function validatePathParameter(path7, paramName, projectRoot = process.cwd()) {
|
|
16997
|
+
if (!path7 || path7.trim() === "") {
|
|
16997
16998
|
throw new ValidationError(
|
|
16998
16999
|
`Invalid ${paramName}: path cannot be empty`,
|
|
16999
17000
|
-32602 /* InvalidParams */,
|
|
17000
|
-
{ path:
|
|
17001
|
+
{ path: path7, paramName }
|
|
17001
17002
|
);
|
|
17002
17003
|
}
|
|
17003
17004
|
for (const pattern of DANGEROUS_PATH_PATTERNS) {
|
|
17004
|
-
if (
|
|
17005
|
+
if (path7.includes(pattern)) {
|
|
17005
17006
|
throw new ValidationError(
|
|
17006
17007
|
`Invalid ${paramName}: path contains dangerous pattern "${pattern}"`,
|
|
17007
17008
|
-32602 /* InvalidParams */,
|
|
17008
|
-
{ path:
|
|
17009
|
+
{ path: path7, paramName, pattern }
|
|
17009
17010
|
);
|
|
17010
17011
|
}
|
|
17011
17012
|
}
|
|
17012
|
-
if (isAbsolute(
|
|
17013
|
+
if (isAbsolute(path7)) {
|
|
17013
17014
|
throw new ValidationError(
|
|
17014
17015
|
`Invalid ${paramName}: absolute paths are not allowed`,
|
|
17015
17016
|
-32602 /* InvalidParams */,
|
|
17016
|
-
{ path:
|
|
17017
|
+
{ path: path7, paramName }
|
|
17017
17018
|
);
|
|
17018
17019
|
}
|
|
17019
17020
|
try {
|
|
17020
|
-
const resolvedPath = resolve(projectRoot,
|
|
17021
|
+
const resolvedPath = resolve(projectRoot, path7);
|
|
17021
17022
|
const normalizedRoot = resolve(projectRoot);
|
|
17022
17023
|
if (!resolvedPath.startsWith(normalizedRoot + sep) && resolvedPath !== normalizedRoot) {
|
|
17023
17024
|
throw new ValidationError(
|
|
17024
17025
|
`Invalid ${paramName}: path escapes project boundary`,
|
|
17025
17026
|
-32602 /* InvalidParams */,
|
|
17026
|
-
{ path:
|
|
17027
|
+
{ path: path7, paramName, projectRoot, resolvedPath }
|
|
17027
17028
|
);
|
|
17028
17029
|
}
|
|
17029
17030
|
} catch (error) {
|
|
17030
17031
|
throw new ValidationError(
|
|
17031
17032
|
`Invalid ${paramName}: path resolution failed`,
|
|
17032
17033
|
-32602 /* InvalidParams */,
|
|
17033
|
-
{ path:
|
|
17034
|
+
{ path: path7, paramName, error: String(error) }
|
|
17034
17035
|
);
|
|
17035
17036
|
}
|
|
17036
|
-
if (
|
|
17037
|
+
if (path7.includes("\0")) {
|
|
17037
17038
|
throw new ValidationError(
|
|
17038
17039
|
`Invalid ${paramName}: path contains null byte`,
|
|
17039
17040
|
-32602 /* InvalidParams */,
|
|
17040
|
-
{ path:
|
|
17041
|
+
{ path: path7, paramName }
|
|
17041
17042
|
);
|
|
17042
17043
|
}
|
|
17043
|
-
if (SUSPICIOUS_PATH_CHARS.test(
|
|
17044
|
+
if (SUSPICIOUS_PATH_CHARS.test(path7)) {
|
|
17044
17045
|
throw new ValidationError(
|
|
17045
17046
|
`Invalid ${paramName}: path contains invalid characters`,
|
|
17046
17047
|
-32602 /* InvalidParams */,
|
|
17047
|
-
{ path:
|
|
17048
|
+
{ path: path7, paramName }
|
|
17048
17049
|
);
|
|
17049
17050
|
}
|
|
17050
17051
|
}
|
|
@@ -17740,8 +17741,8 @@ function createMemoryExportHandler(deps) {
|
|
|
17740
17741
|
return async (input) => {
|
|
17741
17742
|
logger.info("[MCP] memory_export called", { input });
|
|
17742
17743
|
try {
|
|
17743
|
-
const { path:
|
|
17744
|
-
const absolutePath = resolveExportPath(deps.pathResolver,
|
|
17744
|
+
const { path: path7 } = input;
|
|
17745
|
+
const absolutePath = resolveExportPath(deps.pathResolver, path7);
|
|
17745
17746
|
const exported = await deps.memoryManager.exportToJSON(absolutePath);
|
|
17746
17747
|
const result = {
|
|
17747
17748
|
success: true,
|
|
@@ -17777,8 +17778,8 @@ function createMemoryImportHandler(deps) {
|
|
|
17777
17778
|
return async (input) => {
|
|
17778
17779
|
logger.info("[MCP] memory_import called", { input });
|
|
17779
17780
|
try {
|
|
17780
|
-
const { path:
|
|
17781
|
-
const absolutePath = resolveImportPath(deps.pathResolver,
|
|
17781
|
+
const { path: path7 } = input;
|
|
17782
|
+
const absolutePath = resolveImportPath(deps.pathResolver, path7);
|
|
17782
17783
|
const imported = await deps.memoryManager.importFromJSON(absolutePath);
|
|
17783
17784
|
const result = {
|
|
17784
17785
|
success: true,
|
|
@@ -18133,6 +18134,1930 @@ Task: ${task}`;
|
|
|
18133
18134
|
};
|
|
18134
18135
|
}
|
|
18135
18136
|
|
|
18137
|
+
// src/mcp/tools/task/index.ts
|
|
18138
|
+
init_esm_shims();
|
|
18139
|
+
|
|
18140
|
+
// src/mcp/tools/task/create-task.ts
|
|
18141
|
+
init_esm_shims();
|
|
18142
|
+
|
|
18143
|
+
// src/core/task-engine/index.ts
|
|
18144
|
+
init_esm_shims();
|
|
18145
|
+
|
|
18146
|
+
// src/core/task-engine/types.ts
|
|
18147
|
+
init_esm_shims();
|
|
18148
|
+
var TaskEngineError = class _TaskEngineError extends Error {
|
|
18149
|
+
constructor(message, code, details) {
|
|
18150
|
+
super(message);
|
|
18151
|
+
this.code = code;
|
|
18152
|
+
this.details = details;
|
|
18153
|
+
this.name = "TaskEngineError";
|
|
18154
|
+
Error.captureStackTrace?.(this, _TaskEngineError);
|
|
18155
|
+
}
|
|
18156
|
+
};
|
|
18157
|
+
var LoopPreventionError = class extends TaskEngineError {
|
|
18158
|
+
constructor(message, callChain, code = "LOOP_DETECTED") {
|
|
18159
|
+
super(message, code, { callChain });
|
|
18160
|
+
this.callChain = callChain;
|
|
18161
|
+
this.name = "LoopPreventionError";
|
|
18162
|
+
}
|
|
18163
|
+
};
|
|
18164
|
+
var TaskTypeSchema = z.enum([
|
|
18165
|
+
"web_search",
|
|
18166
|
+
"code_review",
|
|
18167
|
+
"code_generation",
|
|
18168
|
+
"analysis",
|
|
18169
|
+
"custom"
|
|
18170
|
+
]);
|
|
18171
|
+
var TaskEngineSchema = z.enum([
|
|
18172
|
+
"auto",
|
|
18173
|
+
"gemini",
|
|
18174
|
+
"claude",
|
|
18175
|
+
"codex",
|
|
18176
|
+
"ax-cli"
|
|
18177
|
+
]);
|
|
18178
|
+
var TaskStatusSchema = z.enum([
|
|
18179
|
+
"pending",
|
|
18180
|
+
"running",
|
|
18181
|
+
"completed",
|
|
18182
|
+
"failed",
|
|
18183
|
+
"expired"
|
|
18184
|
+
]);
|
|
18185
|
+
var OriginClientSchema = z.enum([
|
|
18186
|
+
"claude-code",
|
|
18187
|
+
"gemini-cli",
|
|
18188
|
+
"codex-cli",
|
|
18189
|
+
"ax-cli",
|
|
18190
|
+
"unknown"
|
|
18191
|
+
]);
|
|
18192
|
+
var CreateTaskInputSchema = z.object({
|
|
18193
|
+
type: TaskTypeSchema,
|
|
18194
|
+
payload: z.record(z.string(), z.unknown()),
|
|
18195
|
+
engine: TaskEngineSchema.default("auto"),
|
|
18196
|
+
priority: z.number().int().min(1).max(10).default(5),
|
|
18197
|
+
ttlHours: z.number().int().min(1).default(24).transform((v) => Math.min(v, 168)),
|
|
18198
|
+
context: z.object({
|
|
18199
|
+
originClient: OriginClientSchema.optional(),
|
|
18200
|
+
callChain: z.array(z.string()).optional(),
|
|
18201
|
+
depth: z.number().int().min(0).optional()
|
|
18202
|
+
}).optional()
|
|
18203
|
+
});
|
|
18204
|
+
var TaskFilterSchema = z.object({
|
|
18205
|
+
status: TaskStatusSchema.optional(),
|
|
18206
|
+
engine: TaskEngineSchema.optional(),
|
|
18207
|
+
type: TaskTypeSchema.optional(),
|
|
18208
|
+
originClient: OriginClientSchema.optional(),
|
|
18209
|
+
limit: z.number().int().min(1).max(1e3).default(20),
|
|
18210
|
+
offset: z.number().int().min(0).default(0)
|
|
18211
|
+
});
|
|
18212
|
+
z.object({
|
|
18213
|
+
engineOverride: z.enum(["gemini", "claude", "codex", "ax-cli"]).optional(),
|
|
18214
|
+
timeoutMs: z.number().int().min(5e3).max(3e5).optional(),
|
|
18215
|
+
skipCache: z.boolean().default(false)
|
|
18216
|
+
});
|
|
18217
|
+
function isLoopPreventionError(error) {
|
|
18218
|
+
return error instanceof LoopPreventionError;
|
|
18219
|
+
}
|
|
18220
|
+
|
|
18221
|
+
// src/core/task-engine/loop-guard.ts
|
|
18222
|
+
init_esm_shims();
|
|
18223
|
+
var DEFAULT_BLOCKED_PATTERNS = [
|
|
18224
|
+
// Prevent recursive hub pattern
|
|
18225
|
+
/automatosx.*automatosx/
|
|
18226
|
+
];
|
|
18227
|
+
var CLIENT_NORMALIZATION_MAP = {
|
|
18228
|
+
// Claude variants
|
|
18229
|
+
"claude": "claude-code",
|
|
18230
|
+
"claude-code": "claude-code",
|
|
18231
|
+
"claudecode": "claude-code",
|
|
18232
|
+
"anthropic": "claude-code",
|
|
18233
|
+
// Gemini variants
|
|
18234
|
+
"gemini": "gemini-cli",
|
|
18235
|
+
"gemini-cli": "gemini-cli",
|
|
18236
|
+
"geminicli": "gemini-cli",
|
|
18237
|
+
"google": "gemini-cli",
|
|
18238
|
+
// Codex variants
|
|
18239
|
+
"codex": "codex-cli",
|
|
18240
|
+
"codex-cli": "codex-cli",
|
|
18241
|
+
"openai": "codex-cli",
|
|
18242
|
+
"gpt": "codex-cli",
|
|
18243
|
+
// ax-cli variants
|
|
18244
|
+
"ax": "ax-cli",
|
|
18245
|
+
"ax-cli": "ax-cli",
|
|
18246
|
+
"axcli": "ax-cli"
|
|
18247
|
+
};
|
|
18248
|
+
var LoopGuard = class {
|
|
18249
|
+
config;
|
|
18250
|
+
constructor(config = {}) {
|
|
18251
|
+
this.config = {
|
|
18252
|
+
maxDepth: config.maxDepth ?? 2,
|
|
18253
|
+
maxChainLength: config.maxChainLength ?? 5,
|
|
18254
|
+
blockSelfCalls: config.blockSelfCalls ?? true,
|
|
18255
|
+
blockedPatterns: config.blockedPatterns ?? DEFAULT_BLOCKED_PATTERNS
|
|
18256
|
+
};
|
|
18257
|
+
if (this.config.maxDepth < 1 || this.config.maxDepth > 10) {
|
|
18258
|
+
throw new Error("LoopGuard: maxDepth must be between 1 and 10");
|
|
18259
|
+
}
|
|
18260
|
+
if (this.config.maxChainLength < 2 || this.config.maxChainLength > 20) {
|
|
18261
|
+
throw new Error("LoopGuard: maxChainLength must be between 2 and 20");
|
|
18262
|
+
}
|
|
18263
|
+
}
|
|
18264
|
+
/**
|
|
18265
|
+
* Validate that a task execution is allowed
|
|
18266
|
+
*
|
|
18267
|
+
* @param ctx - Current task context
|
|
18268
|
+
* @param targetEngine - Engine to route the task to
|
|
18269
|
+
* @throws {LoopPreventionError} If execution would cause a loop
|
|
18270
|
+
*/
|
|
18271
|
+
validateExecution(ctx, targetEngine) {
|
|
18272
|
+
const normalizedTarget = this.normalizeClient(targetEngine);
|
|
18273
|
+
const projectedChain = [...ctx.callChain, "automatosx", normalizedTarget];
|
|
18274
|
+
if (ctx.depth >= this.config.maxDepth) {
|
|
18275
|
+
throw new LoopPreventionError(
|
|
18276
|
+
`Maximum depth exceeded: current depth ${ctx.depth} >= limit ${this.config.maxDepth}`,
|
|
18277
|
+
projectedChain,
|
|
18278
|
+
"DEPTH_EXCEEDED"
|
|
18279
|
+
);
|
|
18280
|
+
}
|
|
18281
|
+
if (this.config.blockSelfCalls) {
|
|
18282
|
+
const engineInChain = ctx.callChain.some(
|
|
18283
|
+
(entry) => this.normalizeClient(entry) === normalizedTarget
|
|
18284
|
+
);
|
|
18285
|
+
if (engineInChain) {
|
|
18286
|
+
throw new LoopPreventionError(
|
|
18287
|
+
`Circular call detected: ${normalizedTarget} already in chain [${ctx.callChain.join(" \u2192 ")}]`,
|
|
18288
|
+
projectedChain,
|
|
18289
|
+
"LOOP_DETECTED"
|
|
18290
|
+
);
|
|
18291
|
+
}
|
|
18292
|
+
}
|
|
18293
|
+
if (projectedChain.length > this.config.maxChainLength) {
|
|
18294
|
+
throw new LoopPreventionError(
|
|
18295
|
+
`Call chain too long: ${projectedChain.length} > ${this.config.maxChainLength}`,
|
|
18296
|
+
projectedChain,
|
|
18297
|
+
"CHAIN_TOO_LONG"
|
|
18298
|
+
);
|
|
18299
|
+
}
|
|
18300
|
+
const chainString = projectedChain.join("\u2192");
|
|
18301
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
18302
|
+
if (pattern.test(chainString)) {
|
|
18303
|
+
throw new LoopPreventionError(
|
|
18304
|
+
`Blocked pattern matched: ${chainString} matches ${pattern.toString()}`,
|
|
18305
|
+
projectedChain,
|
|
18306
|
+
"BLOCKED_PATTERN"
|
|
18307
|
+
);
|
|
18308
|
+
}
|
|
18309
|
+
}
|
|
18310
|
+
}
|
|
18311
|
+
/**
|
|
18312
|
+
* Create a new task context for an incoming request
|
|
18313
|
+
*
|
|
18314
|
+
* @param originClient - The client that initiated the request
|
|
18315
|
+
* @param taskId - Optional task ID (will be set later if not provided)
|
|
18316
|
+
* @returns New task context
|
|
18317
|
+
*/
|
|
18318
|
+
createContext(originClient, taskId = "") {
|
|
18319
|
+
const normalized = this.normalizeClient(originClient);
|
|
18320
|
+
return {
|
|
18321
|
+
taskId,
|
|
18322
|
+
originClient: normalized,
|
|
18323
|
+
callChain: [normalized],
|
|
18324
|
+
depth: 0,
|
|
18325
|
+
maxDepth: this.config.maxDepth,
|
|
18326
|
+
createdAt: Date.now()
|
|
18327
|
+
};
|
|
18328
|
+
}
|
|
18329
|
+
/**
|
|
18330
|
+
* Extend a task context for a nested call
|
|
18331
|
+
*
|
|
18332
|
+
* @param ctx - Current task context
|
|
18333
|
+
* @param engine - Engine being called
|
|
18334
|
+
* @returns Extended task context
|
|
18335
|
+
*/
|
|
18336
|
+
extendContext(ctx, engine) {
|
|
18337
|
+
const normalizedEngine = this.normalizeClient(engine);
|
|
18338
|
+
return {
|
|
18339
|
+
...ctx,
|
|
18340
|
+
callChain: [...ctx.callChain, "automatosx", normalizedEngine],
|
|
18341
|
+
depth: ctx.depth + 1,
|
|
18342
|
+
createdAt: Date.now()
|
|
18343
|
+
};
|
|
18344
|
+
}
|
|
18345
|
+
/**
|
|
18346
|
+
* Merge an incoming context with the current context
|
|
18347
|
+
* Used when a task already has context from a previous hop
|
|
18348
|
+
*
|
|
18349
|
+
* @param incomingCtx - Context from incoming request
|
|
18350
|
+
* @param currentOrigin - Current origin (automatosx)
|
|
18351
|
+
* @returns Merged context
|
|
18352
|
+
*/
|
|
18353
|
+
mergeContext(incomingCtx, currentOrigin = "automatosx") {
|
|
18354
|
+
const callChain = incomingCtx.callChain ?? [];
|
|
18355
|
+
const originClient = incomingCtx.originClient ?? "unknown";
|
|
18356
|
+
const mergedChain = callChain.length > 0 ? [...callChain, currentOrigin] : [originClient, currentOrigin];
|
|
18357
|
+
return {
|
|
18358
|
+
taskId: incomingCtx.taskId ?? "",
|
|
18359
|
+
originClient: this.normalizeClient(originClient),
|
|
18360
|
+
callChain: mergedChain,
|
|
18361
|
+
depth: (incomingCtx.depth ?? 0) + 1,
|
|
18362
|
+
maxDepth: this.config.maxDepth,
|
|
18363
|
+
createdAt: Date.now()
|
|
18364
|
+
};
|
|
18365
|
+
}
|
|
18366
|
+
/**
|
|
18367
|
+
* Get a human-readable representation of the call chain
|
|
18368
|
+
*
|
|
18369
|
+
* @param ctx - Task context
|
|
18370
|
+
* @returns Formatted call chain string
|
|
18371
|
+
*/
|
|
18372
|
+
getCallChainString(ctx) {
|
|
18373
|
+
return ctx.callChain.join(" \u2192 ");
|
|
18374
|
+
}
|
|
18375
|
+
/**
|
|
18376
|
+
* Check if a context is valid (not expired or corrupted)
|
|
18377
|
+
*
|
|
18378
|
+
* @param ctx - Task context to validate
|
|
18379
|
+
* @returns true if context is valid
|
|
18380
|
+
*/
|
|
18381
|
+
isValidContext(ctx) {
|
|
18382
|
+
if (!ctx || typeof ctx !== "object") {
|
|
18383
|
+
return false;
|
|
18384
|
+
}
|
|
18385
|
+
const context = ctx;
|
|
18386
|
+
return typeof context.taskId === "string" && typeof context.originClient === "string" && Array.isArray(context.callChain) && context.callChain.every((item) => typeof item === "string") && typeof context.depth === "number" && Number.isInteger(context.depth) && context.depth >= 0 && typeof context.maxDepth === "number" && typeof context.createdAt === "number";
|
|
18387
|
+
}
|
|
18388
|
+
/**
|
|
18389
|
+
* Get current configuration
|
|
18390
|
+
*/
|
|
18391
|
+
getConfig() {
|
|
18392
|
+
return { ...this.config };
|
|
18393
|
+
}
|
|
18394
|
+
/**
|
|
18395
|
+
* Normalize client name to standard format
|
|
18396
|
+
*
|
|
18397
|
+
* IMPORTANT: Unknown engines are preserved as-is (normalized formatting only)
|
|
18398
|
+
* to prevent false-positive loop detection when two different unknown engines
|
|
18399
|
+
* would both map to 'unknown' and trigger a circular call error.
|
|
18400
|
+
*/
|
|
18401
|
+
normalizeClient(client) {
|
|
18402
|
+
if (!client || typeof client !== "string") {
|
|
18403
|
+
return "unknown";
|
|
18404
|
+
}
|
|
18405
|
+
const normalized = client.toLowerCase().trim().replace(/[\s-_]+/g, "-");
|
|
18406
|
+
if (CLIENT_NORMALIZATION_MAP[normalized]) {
|
|
18407
|
+
return CLIENT_NORMALIZATION_MAP[normalized];
|
|
18408
|
+
}
|
|
18409
|
+
return normalized;
|
|
18410
|
+
}
|
|
18411
|
+
};
|
|
18412
|
+
function createLoopGuard(config) {
|
|
18413
|
+
return new LoopGuard(config);
|
|
18414
|
+
}
|
|
18415
|
+
|
|
18416
|
+
// src/core/task-engine/compression.ts
|
|
18417
|
+
init_esm_shims();
|
|
18418
|
+
var DEFAULT_COMPRESSION_LEVEL = 6;
|
|
18419
|
+
var MIN_COMPRESSION_THRESHOLD = 4096;
|
|
18420
|
+
function decompressPayload(compressed) {
|
|
18421
|
+
try {
|
|
18422
|
+
const decompressed = gunzipSync(compressed);
|
|
18423
|
+
const json = decompressed.toString("utf-8");
|
|
18424
|
+
return JSON.parse(json);
|
|
18425
|
+
} catch (error) {
|
|
18426
|
+
throw new TaskEngineError(
|
|
18427
|
+
`Failed to decompress payload: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
18428
|
+
"COMPRESSION_ERROR",
|
|
18429
|
+
{ originalError: error }
|
|
18430
|
+
);
|
|
18431
|
+
}
|
|
18432
|
+
}
|
|
18433
|
+
function compressWithInfo(payload, options = {}) {
|
|
18434
|
+
const level = options.level ?? DEFAULT_COMPRESSION_LEVEL;
|
|
18435
|
+
const minThreshold = options.minThreshold ?? MIN_COMPRESSION_THRESHOLD;
|
|
18436
|
+
try {
|
|
18437
|
+
const json = JSON.stringify(payload);
|
|
18438
|
+
const originalBuffer = Buffer.from(json, "utf-8");
|
|
18439
|
+
const originalSize = originalBuffer.length;
|
|
18440
|
+
if (originalSize < minThreshold) {
|
|
18441
|
+
return {
|
|
18442
|
+
data: originalBuffer,
|
|
18443
|
+
originalSize,
|
|
18444
|
+
compressedSize: originalSize,
|
|
18445
|
+
ratio: 1,
|
|
18446
|
+
compressed: false
|
|
18447
|
+
};
|
|
18448
|
+
}
|
|
18449
|
+
const compressedBuffer = gzipSync(originalBuffer, {
|
|
18450
|
+
level: Math.min(Math.max(level, 1), 9)
|
|
18451
|
+
});
|
|
18452
|
+
const compressedSize = compressedBuffer.length;
|
|
18453
|
+
if (compressedSize >= originalSize) {
|
|
18454
|
+
return {
|
|
18455
|
+
data: originalBuffer,
|
|
18456
|
+
originalSize,
|
|
18457
|
+
compressedSize: originalSize,
|
|
18458
|
+
ratio: 1,
|
|
18459
|
+
compressed: false
|
|
18460
|
+
};
|
|
18461
|
+
}
|
|
18462
|
+
return {
|
|
18463
|
+
data: compressedBuffer,
|
|
18464
|
+
originalSize,
|
|
18465
|
+
compressedSize,
|
|
18466
|
+
ratio: originalSize / compressedSize,
|
|
18467
|
+
compressed: true
|
|
18468
|
+
};
|
|
18469
|
+
} catch (error) {
|
|
18470
|
+
throw new TaskEngineError(
|
|
18471
|
+
`Failed to compress payload: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
18472
|
+
"COMPRESSION_ERROR",
|
|
18473
|
+
{ originalError: error }
|
|
18474
|
+
);
|
|
18475
|
+
}
|
|
18476
|
+
}
|
|
18477
|
+
|
|
18478
|
+
// src/core/task-engine/store.ts
|
|
18479
|
+
init_esm_shims();
|
|
18480
|
+
init_factory();
|
|
18481
|
+
init_logger();
|
|
18482
|
+
var DEFAULT_CONFIG3 = {
|
|
18483
|
+
dbPath: ".automatosx/tasks/tasks.db",
|
|
18484
|
+
maxPayloadBytes: 1024 * 1024,
|
|
18485
|
+
// 1MB
|
|
18486
|
+
compressionEnabled: true,
|
|
18487
|
+
compressionLevel: 6,
|
|
18488
|
+
defaultTtlHours: 24,
|
|
18489
|
+
maxTtlHours: 168,
|
|
18490
|
+
// 7 days
|
|
18491
|
+
busyTimeout: 5e3
|
|
18492
|
+
};
|
|
18493
|
+
var SQL = {
|
|
18494
|
+
CREATE_TABLE: `
|
|
18495
|
+
CREATE TABLE IF NOT EXISTS task_store (
|
|
18496
|
+
id TEXT PRIMARY KEY,
|
|
18497
|
+
type TEXT NOT NULL CHECK (type IN ('web_search', 'code_review', 'code_generation', 'analysis', 'custom')),
|
|
18498
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed', 'expired')),
|
|
18499
|
+
engine TEXT CHECK (engine IN ('gemini', 'claude', 'codex', 'ax-cli', NULL)),
|
|
18500
|
+
priority INTEGER NOT NULL DEFAULT 5 CHECK (priority BETWEEN 1 AND 10),
|
|
18501
|
+
|
|
18502
|
+
payload_compressed BLOB NOT NULL,
|
|
18503
|
+
payload_size_bytes INTEGER NOT NULL,
|
|
18504
|
+
payload_hash TEXT NOT NULL,
|
|
18505
|
+
is_compressed INTEGER NOT NULL DEFAULT 1,
|
|
18506
|
+
|
|
18507
|
+
result_compressed BLOB,
|
|
18508
|
+
result_size_bytes INTEGER,
|
|
18509
|
+
result_is_compressed INTEGER,
|
|
18510
|
+
|
|
18511
|
+
origin_client TEXT NOT NULL,
|
|
18512
|
+
call_chain TEXT NOT NULL,
|
|
18513
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
18514
|
+
|
|
18515
|
+
created_at INTEGER NOT NULL,
|
|
18516
|
+
started_at INTEGER,
|
|
18517
|
+
completed_at INTEGER,
|
|
18518
|
+
expires_at INTEGER NOT NULL,
|
|
18519
|
+
|
|
18520
|
+
duration_ms INTEGER,
|
|
18521
|
+
tokens_prompt INTEGER,
|
|
18522
|
+
tokens_completion INTEGER,
|
|
18523
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
18524
|
+
|
|
18525
|
+
error_code TEXT,
|
|
18526
|
+
error_message TEXT
|
|
18527
|
+
)
|
|
18528
|
+
`,
|
|
18529
|
+
CREATE_INDEXES: `
|
|
18530
|
+
CREATE INDEX IF NOT EXISTS idx_task_status ON task_store(status);
|
|
18531
|
+
CREATE INDEX IF NOT EXISTS idx_task_status_priority ON task_store(status, priority DESC);
|
|
18532
|
+
CREATE INDEX IF NOT EXISTS idx_task_expires ON task_store(expires_at);
|
|
18533
|
+
CREATE INDEX IF NOT EXISTS idx_task_engine ON task_store(engine);
|
|
18534
|
+
CREATE INDEX IF NOT EXISTS idx_task_created ON task_store(created_at DESC);
|
|
18535
|
+
CREATE INDEX IF NOT EXISTS idx_task_payload_hash ON task_store(payload_hash);
|
|
18536
|
+
CREATE INDEX IF NOT EXISTS idx_task_origin ON task_store(origin_client);
|
|
18537
|
+
`,
|
|
18538
|
+
INSERT_TASK: `
|
|
18539
|
+
INSERT INTO task_store (
|
|
18540
|
+
id, type, status, engine, priority,
|
|
18541
|
+
payload_compressed, payload_size_bytes, payload_hash, is_compressed,
|
|
18542
|
+
origin_client, call_chain, depth,
|
|
18543
|
+
created_at, expires_at
|
|
18544
|
+
) VALUES (
|
|
18545
|
+
:id, :type, :status, :engine, :priority,
|
|
18546
|
+
:payload_compressed, :payload_size_bytes, :payload_hash, :is_compressed,
|
|
18547
|
+
:origin_client, :call_chain, :depth,
|
|
18548
|
+
:created_at, :expires_at
|
|
18549
|
+
)
|
|
18550
|
+
`,
|
|
18551
|
+
GET_BY_ID: `
|
|
18552
|
+
SELECT * FROM task_store WHERE id = ?
|
|
18553
|
+
`,
|
|
18554
|
+
UPDATE_STATUS: `
|
|
18555
|
+
UPDATE task_store SET
|
|
18556
|
+
status = :status,
|
|
18557
|
+
started_at = COALESCE(:started_at, started_at),
|
|
18558
|
+
completed_at = COALESCE(:completed_at, completed_at),
|
|
18559
|
+
error_code = COALESCE(:error_code, error_code),
|
|
18560
|
+
error_message = COALESCE(:error_message, error_message)
|
|
18561
|
+
WHERE id = :id
|
|
18562
|
+
`,
|
|
18563
|
+
UPDATE_RESULT: `
|
|
18564
|
+
UPDATE task_store SET
|
|
18565
|
+
status = 'completed',
|
|
18566
|
+
result_compressed = :result_compressed,
|
|
18567
|
+
result_size_bytes = :result_size_bytes,
|
|
18568
|
+
result_is_compressed = :result_is_compressed,
|
|
18569
|
+
completed_at = :completed_at,
|
|
18570
|
+
duration_ms = :duration_ms,
|
|
18571
|
+
tokens_prompt = :tokens_prompt,
|
|
18572
|
+
tokens_completion = :tokens_completion
|
|
18573
|
+
WHERE id = :id
|
|
18574
|
+
`,
|
|
18575
|
+
UPDATE_FAILED: `
|
|
18576
|
+
UPDATE task_store SET
|
|
18577
|
+
status = 'failed',
|
|
18578
|
+
completed_at = :completed_at,
|
|
18579
|
+
duration_ms = :duration_ms,
|
|
18580
|
+
error_code = :error_code,
|
|
18581
|
+
error_message = :error_message,
|
|
18582
|
+
retry_count = retry_count + 1
|
|
18583
|
+
WHERE id = :id
|
|
18584
|
+
`,
|
|
18585
|
+
INCREMENT_RETRY: `
|
|
18586
|
+
UPDATE task_store SET retry_count = retry_count + 1 WHERE id = ?
|
|
18587
|
+
`,
|
|
18588
|
+
DELETE_TASK: `
|
|
18589
|
+
DELETE FROM task_store WHERE id = ?
|
|
18590
|
+
`,
|
|
18591
|
+
CLEANUP_EXPIRED: `
|
|
18592
|
+
DELETE FROM task_store WHERE expires_at < ? AND status NOT IN ('running')
|
|
18593
|
+
`,
|
|
18594
|
+
CLEANUP_ZOMBIE_RUNNING: `
|
|
18595
|
+
UPDATE task_store SET
|
|
18596
|
+
status = 'failed',
|
|
18597
|
+
completed_at = :completed_at,
|
|
18598
|
+
error_code = 'ZOMBIE_TASK',
|
|
18599
|
+
error_message = 'Task was stuck in running state past expiry'
|
|
18600
|
+
WHERE status = 'running' AND expires_at < :now
|
|
18601
|
+
`,
|
|
18602
|
+
COUNT_BY_STATUS: `
|
|
18603
|
+
SELECT status, COUNT(*) as count FROM task_store GROUP BY status
|
|
18604
|
+
`,
|
|
18605
|
+
FIND_BY_HASH: `
|
|
18606
|
+
SELECT id FROM task_store WHERE payload_hash = ? AND status = 'completed' LIMIT 1
|
|
18607
|
+
`
|
|
18608
|
+
};
|
|
18609
|
+
var TaskStore = class {
|
|
18610
|
+
db;
|
|
18611
|
+
config;
|
|
18612
|
+
closed = false;
|
|
18613
|
+
// Prepared statements
|
|
18614
|
+
stmtInsert;
|
|
18615
|
+
stmtGetById;
|
|
18616
|
+
stmtUpdateStatus;
|
|
18617
|
+
stmtUpdateResult;
|
|
18618
|
+
stmtUpdateFailed;
|
|
18619
|
+
stmtIncrementRetry;
|
|
18620
|
+
stmtDelete;
|
|
18621
|
+
stmtCleanup;
|
|
18622
|
+
stmtCleanupZombies;
|
|
18623
|
+
stmtCountByStatus;
|
|
18624
|
+
stmtFindByHash;
|
|
18625
|
+
constructor(config = {}) {
|
|
18626
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
18627
|
+
this.db = DatabaseFactory.create(this.config.dbPath, {
|
|
18628
|
+
busyTimeout: this.config.busyTimeout,
|
|
18629
|
+
enableWal: true
|
|
18630
|
+
});
|
|
18631
|
+
this.initializeSchema();
|
|
18632
|
+
this.prepareStatements();
|
|
18633
|
+
logger.debug("TaskStore initialized", {
|
|
18634
|
+
dbPath: this.config.dbPath,
|
|
18635
|
+
maxPayloadBytes: this.config.maxPayloadBytes,
|
|
18636
|
+
compressionEnabled: this.config.compressionEnabled
|
|
18637
|
+
});
|
|
18638
|
+
}
|
|
18639
|
+
/**
|
|
18640
|
+
* Create a new task
|
|
18641
|
+
*/
|
|
18642
|
+
createTask(input) {
|
|
18643
|
+
this.ensureOpen();
|
|
18644
|
+
const validated = CreateTaskInputSchema.parse(input);
|
|
18645
|
+
const payloadJson = JSON.stringify(validated.payload);
|
|
18646
|
+
const payloadSize = Buffer.byteLength(payloadJson, "utf-8");
|
|
18647
|
+
if (payloadSize > this.config.maxPayloadBytes) {
|
|
18648
|
+
throw new TaskEngineError(
|
|
18649
|
+
`Payload size ${payloadSize} exceeds limit ${this.config.maxPayloadBytes}`,
|
|
18650
|
+
"PAYLOAD_TOO_LARGE",
|
|
18651
|
+
{ payloadSize, limit: this.config.maxPayloadBytes }
|
|
18652
|
+
);
|
|
18653
|
+
}
|
|
18654
|
+
let payloadBuffer;
|
|
18655
|
+
let isCompressed;
|
|
18656
|
+
let compressionRatio;
|
|
18657
|
+
if (this.config.compressionEnabled) {
|
|
18658
|
+
const result = compressWithInfo(validated.payload, {
|
|
18659
|
+
level: this.config.compressionLevel
|
|
18660
|
+
});
|
|
18661
|
+
payloadBuffer = result.data;
|
|
18662
|
+
isCompressed = result.compressed;
|
|
18663
|
+
compressionRatio = result.ratio;
|
|
18664
|
+
} else {
|
|
18665
|
+
payloadBuffer = Buffer.from(payloadJson, "utf-8");
|
|
18666
|
+
isCompressed = false;
|
|
18667
|
+
compressionRatio = 1;
|
|
18668
|
+
}
|
|
18669
|
+
const taskId = this.generateTaskId();
|
|
18670
|
+
const payloadHash = this.hashPayload(payloadJson);
|
|
18671
|
+
const now = Date.now();
|
|
18672
|
+
const ttlHours = Math.min(
|
|
18673
|
+
validated.ttlHours ?? this.config.defaultTtlHours,
|
|
18674
|
+
this.config.maxTtlHours
|
|
18675
|
+
);
|
|
18676
|
+
const expiresAt = now + ttlHours * 60 * 60 * 1e3;
|
|
18677
|
+
const estimatedEngine = validated.engine === "auto" ? this.estimateEngine(validated.type) : validated.engine;
|
|
18678
|
+
try {
|
|
18679
|
+
this.stmtInsert.run({
|
|
18680
|
+
id: taskId,
|
|
18681
|
+
type: validated.type,
|
|
18682
|
+
status: "pending",
|
|
18683
|
+
engine: validated.engine === "auto" ? null : validated.engine,
|
|
18684
|
+
priority: validated.priority ?? 5,
|
|
18685
|
+
payload_compressed: payloadBuffer,
|
|
18686
|
+
payload_size_bytes: payloadSize,
|
|
18687
|
+
payload_hash: payloadHash,
|
|
18688
|
+
is_compressed: isCompressed ? 1 : 0,
|
|
18689
|
+
origin_client: validated.context?.originClient ?? "unknown",
|
|
18690
|
+
call_chain: JSON.stringify(validated.context?.callChain ?? []),
|
|
18691
|
+
depth: validated.context?.depth ?? 0,
|
|
18692
|
+
created_at: now,
|
|
18693
|
+
expires_at: expiresAt
|
|
18694
|
+
});
|
|
18695
|
+
} catch (error) {
|
|
18696
|
+
throw new TaskEngineError(
|
|
18697
|
+
`Failed to create task: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
18698
|
+
"STORE_ERROR",
|
|
18699
|
+
{ originalError: error }
|
|
18700
|
+
);
|
|
18701
|
+
}
|
|
18702
|
+
logger.debug("Task created", {
|
|
18703
|
+
taskId,
|
|
18704
|
+
type: validated.type,
|
|
18705
|
+
payloadSize,
|
|
18706
|
+
compressedSize: payloadBuffer.length,
|
|
18707
|
+
compressionRatio: compressionRatio.toFixed(2)
|
|
18708
|
+
});
|
|
18709
|
+
return {
|
|
18710
|
+
id: taskId,
|
|
18711
|
+
status: "pending",
|
|
18712
|
+
estimatedEngine: estimatedEngine ?? null,
|
|
18713
|
+
expiresAt,
|
|
18714
|
+
payloadSize,
|
|
18715
|
+
compressionRatio
|
|
18716
|
+
};
|
|
18717
|
+
}
|
|
18718
|
+
/**
|
|
18719
|
+
* Get a task by ID
|
|
18720
|
+
*/
|
|
18721
|
+
getTask(taskId) {
|
|
18722
|
+
this.ensureOpen();
|
|
18723
|
+
const row = this.stmtGetById.get(taskId);
|
|
18724
|
+
if (!row) {
|
|
18725
|
+
return null;
|
|
18726
|
+
}
|
|
18727
|
+
return this.rowToTask(row);
|
|
18728
|
+
}
|
|
18729
|
+
/**
|
|
18730
|
+
* Update task status
|
|
18731
|
+
*/
|
|
18732
|
+
updateTaskStatus(taskId, status, error) {
|
|
18733
|
+
this.ensureOpen();
|
|
18734
|
+
const now = Date.now();
|
|
18735
|
+
const params = {
|
|
18736
|
+
id: taskId,
|
|
18737
|
+
status,
|
|
18738
|
+
started_at: status === "running" ? now : null,
|
|
18739
|
+
completed_at: status === "completed" || status === "failed" ? now : null,
|
|
18740
|
+
error_code: error?.code ?? null,
|
|
18741
|
+
error_message: error?.message ?? null
|
|
18742
|
+
};
|
|
18743
|
+
const result = this.stmtUpdateStatus.run(params);
|
|
18744
|
+
if (result.changes === 0) {
|
|
18745
|
+
throw new TaskEngineError(
|
|
18746
|
+
`Task not found: ${taskId}`,
|
|
18747
|
+
"TASK_NOT_FOUND"
|
|
18748
|
+
);
|
|
18749
|
+
}
|
|
18750
|
+
logger.debug("Task status updated", { taskId, status });
|
|
18751
|
+
}
|
|
18752
|
+
/**
|
|
18753
|
+
* Update task with successful result
|
|
18754
|
+
*/
|
|
18755
|
+
updateTaskResult(taskId, result, metrics) {
|
|
18756
|
+
this.ensureOpen();
|
|
18757
|
+
const now = Date.now();
|
|
18758
|
+
const resultJson = JSON.stringify(result);
|
|
18759
|
+
const resultSize = Buffer.byteLength(resultJson, "utf-8");
|
|
18760
|
+
let resultBuffer;
|
|
18761
|
+
let resultIsCompressed;
|
|
18762
|
+
if (this.config.compressionEnabled) {
|
|
18763
|
+
const compressed = compressWithInfo(result, {
|
|
18764
|
+
level: this.config.compressionLevel
|
|
18765
|
+
});
|
|
18766
|
+
resultBuffer = compressed.data;
|
|
18767
|
+
resultIsCompressed = compressed.compressed;
|
|
18768
|
+
} else {
|
|
18769
|
+
resultBuffer = Buffer.from(resultJson, "utf-8");
|
|
18770
|
+
resultIsCompressed = false;
|
|
18771
|
+
}
|
|
18772
|
+
const dbResult = this.stmtUpdateResult.run({
|
|
18773
|
+
id: taskId,
|
|
18774
|
+
result_compressed: resultBuffer,
|
|
18775
|
+
result_size_bytes: resultSize,
|
|
18776
|
+
result_is_compressed: resultIsCompressed ? 1 : 0,
|
|
18777
|
+
completed_at: now,
|
|
18778
|
+
duration_ms: metrics.durationMs ?? null,
|
|
18779
|
+
tokens_prompt: metrics.tokensPrompt ?? null,
|
|
18780
|
+
tokens_completion: metrics.tokensCompletion ?? null
|
|
18781
|
+
});
|
|
18782
|
+
if (dbResult.changes === 0) {
|
|
18783
|
+
throw new TaskEngineError(
|
|
18784
|
+
`Task not found: ${taskId}`,
|
|
18785
|
+
"TASK_NOT_FOUND"
|
|
18786
|
+
);
|
|
18787
|
+
}
|
|
18788
|
+
logger.debug("Task result updated", {
|
|
18789
|
+
taskId,
|
|
18790
|
+
resultSize,
|
|
18791
|
+
durationMs: metrics.durationMs
|
|
18792
|
+
});
|
|
18793
|
+
}
|
|
18794
|
+
/**
|
|
18795
|
+
* Update task with failure
|
|
18796
|
+
*/
|
|
18797
|
+
updateTaskFailed(taskId, error, durationMs) {
|
|
18798
|
+
this.ensureOpen();
|
|
18799
|
+
const now = Date.now();
|
|
18800
|
+
const result = this.stmtUpdateFailed.run({
|
|
18801
|
+
id: taskId,
|
|
18802
|
+
completed_at: now,
|
|
18803
|
+
duration_ms: durationMs ?? null,
|
|
18804
|
+
error_code: error.code,
|
|
18805
|
+
error_message: error.message
|
|
18806
|
+
});
|
|
18807
|
+
if (result.changes === 0) {
|
|
18808
|
+
throw new TaskEngineError(
|
|
18809
|
+
`Task not found: ${taskId}`,
|
|
18810
|
+
"TASK_NOT_FOUND"
|
|
18811
|
+
);
|
|
18812
|
+
}
|
|
18813
|
+
logger.debug("Task marked as failed", { taskId, error: error.code });
|
|
18814
|
+
}
|
|
18815
|
+
/**
|
|
18816
|
+
* Increment retry count
|
|
18817
|
+
*/
|
|
18818
|
+
incrementRetry(taskId) {
|
|
18819
|
+
this.ensureOpen();
|
|
18820
|
+
this.stmtIncrementRetry.run(taskId);
|
|
18821
|
+
}
|
|
18822
|
+
/**
|
|
18823
|
+
* Delete a task
|
|
18824
|
+
*/
|
|
18825
|
+
deleteTask(taskId) {
|
|
18826
|
+
this.ensureOpen();
|
|
18827
|
+
const result = this.stmtDelete.run(taskId);
|
|
18828
|
+
const deleted = result.changes > 0;
|
|
18829
|
+
if (deleted) {
|
|
18830
|
+
logger.debug("Task deleted", { taskId });
|
|
18831
|
+
}
|
|
18832
|
+
return deleted;
|
|
18833
|
+
}
|
|
18834
|
+
/**
|
|
18835
|
+
* List tasks with optional filtering
|
|
18836
|
+
*/
|
|
18837
|
+
listTasks(filter = {}) {
|
|
18838
|
+
this.ensureOpen();
|
|
18839
|
+
const validated = TaskFilterSchema.parse(filter);
|
|
18840
|
+
const conditions = ["1=1"];
|
|
18841
|
+
const params = {};
|
|
18842
|
+
if (validated.status) {
|
|
18843
|
+
conditions.push("status = :status");
|
|
18844
|
+
params.status = validated.status;
|
|
18845
|
+
}
|
|
18846
|
+
if (validated.engine) {
|
|
18847
|
+
conditions.push("engine = :engine");
|
|
18848
|
+
params.engine = validated.engine;
|
|
18849
|
+
}
|
|
18850
|
+
if (validated.type) {
|
|
18851
|
+
conditions.push("type = :type");
|
|
18852
|
+
params.type = validated.type;
|
|
18853
|
+
}
|
|
18854
|
+
if (validated.originClient) {
|
|
18855
|
+
conditions.push("origin_client = :origin_client");
|
|
18856
|
+
params.origin_client = validated.originClient;
|
|
18857
|
+
}
|
|
18858
|
+
const query = `
|
|
18859
|
+
SELECT * FROM task_store
|
|
18860
|
+
WHERE ${conditions.join(" AND ")}
|
|
18861
|
+
ORDER BY priority DESC, created_at ASC
|
|
18862
|
+
LIMIT :limit OFFSET :offset
|
|
18863
|
+
`;
|
|
18864
|
+
params.limit = validated.limit ?? 20;
|
|
18865
|
+
params.offset = validated.offset ?? 0;
|
|
18866
|
+
const stmt = this.db.prepare(query);
|
|
18867
|
+
const rows = stmt.all(params);
|
|
18868
|
+
return rows.map((row) => this.rowToTask(row));
|
|
18869
|
+
}
|
|
18870
|
+
/**
|
|
18871
|
+
* Count tasks by status
|
|
18872
|
+
*/
|
|
18873
|
+
countByStatus() {
|
|
18874
|
+
this.ensureOpen();
|
|
18875
|
+
const rows = this.stmtCountByStatus.all();
|
|
18876
|
+
const counts = {
|
|
18877
|
+
pending: 0,
|
|
18878
|
+
running: 0,
|
|
18879
|
+
completed: 0,
|
|
18880
|
+
failed: 0,
|
|
18881
|
+
expired: 0
|
|
18882
|
+
};
|
|
18883
|
+
for (const row of rows) {
|
|
18884
|
+
counts[row.status] = row.count;
|
|
18885
|
+
}
|
|
18886
|
+
return counts;
|
|
18887
|
+
}
|
|
18888
|
+
/**
|
|
18889
|
+
* Count tasks matching filter (for pagination)
|
|
18890
|
+
*/
|
|
18891
|
+
countTasks(filter = {}) {
|
|
18892
|
+
this.ensureOpen();
|
|
18893
|
+
const validated = TaskFilterSchema.parse(filter);
|
|
18894
|
+
const conditions = ["1=1"];
|
|
18895
|
+
const params = {};
|
|
18896
|
+
if (validated.status) {
|
|
18897
|
+
conditions.push("status = :status");
|
|
18898
|
+
params.status = validated.status;
|
|
18899
|
+
}
|
|
18900
|
+
if (validated.engine) {
|
|
18901
|
+
conditions.push("engine = :engine");
|
|
18902
|
+
params.engine = validated.engine;
|
|
18903
|
+
}
|
|
18904
|
+
if (validated.type) {
|
|
18905
|
+
conditions.push("type = :type");
|
|
18906
|
+
params.type = validated.type;
|
|
18907
|
+
}
|
|
18908
|
+
if (validated.originClient) {
|
|
18909
|
+
conditions.push("origin_client = :origin_client");
|
|
18910
|
+
params.origin_client = validated.originClient;
|
|
18911
|
+
}
|
|
18912
|
+
const query = `SELECT COUNT(*) as count FROM task_store WHERE ${conditions.join(" AND ")}`;
|
|
18913
|
+
const stmt = this.db.prepare(query);
|
|
18914
|
+
const row = stmt.get(params);
|
|
18915
|
+
return row.count;
|
|
18916
|
+
}
|
|
18917
|
+
/**
|
|
18918
|
+
* Find a completed task with the same payload (for caching)
|
|
18919
|
+
*/
|
|
18920
|
+
findByPayloadHash(payloadHash) {
|
|
18921
|
+
this.ensureOpen();
|
|
18922
|
+
const row = this.stmtFindByHash.get(payloadHash);
|
|
18923
|
+
return row?.id ?? null;
|
|
18924
|
+
}
|
|
18925
|
+
/**
|
|
18926
|
+
* Cleanup expired tasks
|
|
18927
|
+
*/
|
|
18928
|
+
cleanupExpired() {
|
|
18929
|
+
this.ensureOpen();
|
|
18930
|
+
const now = Date.now();
|
|
18931
|
+
const result = this.stmtCleanup.run(now);
|
|
18932
|
+
if (result.changes > 0) {
|
|
18933
|
+
logger.debug("Expired tasks cleaned up", { count: result.changes });
|
|
18934
|
+
}
|
|
18935
|
+
return result.changes;
|
|
18936
|
+
}
|
|
18937
|
+
/**
|
|
18938
|
+
* Mark zombie running tasks as failed.
|
|
18939
|
+
* These are tasks that have been in 'running' state past their expiry time,
|
|
18940
|
+
* likely due to process crashes or other failures.
|
|
18941
|
+
*/
|
|
18942
|
+
cleanupZombieRunning() {
|
|
18943
|
+
this.ensureOpen();
|
|
18944
|
+
const now = Date.now();
|
|
18945
|
+
const result = this.stmtCleanupZombies.run({
|
|
18946
|
+
now,
|
|
18947
|
+
completed_at: now
|
|
18948
|
+
});
|
|
18949
|
+
if (result.changes > 0) {
|
|
18950
|
+
logger.warn("Zombie running tasks marked as failed", { count: result.changes });
|
|
18951
|
+
}
|
|
18952
|
+
return result.changes;
|
|
18953
|
+
}
|
|
18954
|
+
/**
|
|
18955
|
+
* Full cleanup: handles both expired non-running tasks and zombie running tasks.
|
|
18956
|
+
* Call this periodically to prevent resource leaks.
|
|
18957
|
+
*/
|
|
18958
|
+
cleanupAll() {
|
|
18959
|
+
const zombies = this.cleanupZombieRunning();
|
|
18960
|
+
const expired = this.cleanupExpired();
|
|
18961
|
+
return { expired, zombies };
|
|
18962
|
+
}
|
|
18963
|
+
/**
|
|
18964
|
+
* Get store statistics
|
|
18965
|
+
*/
|
|
18966
|
+
getStats() {
|
|
18967
|
+
this.ensureOpen();
|
|
18968
|
+
const counts = this.countByStatus();
|
|
18969
|
+
const totalTasks = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
18970
|
+
let dbSizeBytes = 0;
|
|
18971
|
+
try {
|
|
18972
|
+
const pageCount = this.db.pragma("page_count", { simple: true });
|
|
18973
|
+
const pageSize = this.db.pragma("page_size", { simple: true });
|
|
18974
|
+
dbSizeBytes = pageCount * pageSize;
|
|
18975
|
+
} catch {
|
|
18976
|
+
}
|
|
18977
|
+
return {
|
|
18978
|
+
totalTasks,
|
|
18979
|
+
byStatus: counts,
|
|
18980
|
+
dbSizeBytes
|
|
18981
|
+
};
|
|
18982
|
+
}
|
|
18983
|
+
/**
|
|
18984
|
+
* Close the store
|
|
18985
|
+
*/
|
|
18986
|
+
close() {
|
|
18987
|
+
if (this.closed) return;
|
|
18988
|
+
DatabaseFactory.close(this.db);
|
|
18989
|
+
this.closed = true;
|
|
18990
|
+
logger.debug("TaskStore closed");
|
|
18991
|
+
}
|
|
18992
|
+
// ============================================================================
|
|
18993
|
+
// Private Methods
|
|
18994
|
+
// ============================================================================
|
|
18995
|
+
initializeSchema() {
|
|
18996
|
+
this.db.exec(SQL.CREATE_TABLE);
|
|
18997
|
+
const indexStatements = SQL.CREATE_INDEXES.split(";").filter((s) => s.trim());
|
|
18998
|
+
for (const stmt of indexStatements) {
|
|
18999
|
+
this.db.exec(stmt);
|
|
19000
|
+
}
|
|
19001
|
+
}
|
|
19002
|
+
prepareStatements() {
|
|
19003
|
+
this.stmtInsert = this.db.prepare(SQL.INSERT_TASK);
|
|
19004
|
+
this.stmtGetById = this.db.prepare(SQL.GET_BY_ID);
|
|
19005
|
+
this.stmtUpdateStatus = this.db.prepare(SQL.UPDATE_STATUS);
|
|
19006
|
+
this.stmtUpdateResult = this.db.prepare(SQL.UPDATE_RESULT);
|
|
19007
|
+
this.stmtUpdateFailed = this.db.prepare(SQL.UPDATE_FAILED);
|
|
19008
|
+
this.stmtIncrementRetry = this.db.prepare(SQL.INCREMENT_RETRY);
|
|
19009
|
+
this.stmtDelete = this.db.prepare(SQL.DELETE_TASK);
|
|
19010
|
+
this.stmtCleanup = this.db.prepare(SQL.CLEANUP_EXPIRED);
|
|
19011
|
+
this.stmtCleanupZombies = this.db.prepare(SQL.CLEANUP_ZOMBIE_RUNNING);
|
|
19012
|
+
this.stmtCountByStatus = this.db.prepare(SQL.COUNT_BY_STATUS);
|
|
19013
|
+
this.stmtFindByHash = this.db.prepare(SQL.FIND_BY_HASH);
|
|
19014
|
+
}
|
|
19015
|
+
ensureOpen() {
|
|
19016
|
+
if (this.closed) {
|
|
19017
|
+
throw new TaskEngineError("TaskStore is closed", "STORE_ERROR");
|
|
19018
|
+
}
|
|
19019
|
+
}
|
|
19020
|
+
generateTaskId() {
|
|
19021
|
+
const timestamp = Date.now().toString(36);
|
|
19022
|
+
const random = randomBytes(4).toString("hex");
|
|
19023
|
+
return `task_${timestamp}${random}`;
|
|
19024
|
+
}
|
|
19025
|
+
hashPayload(json) {
|
|
19026
|
+
return createHash("sha256").update(json).digest("hex").substring(0, 16);
|
|
19027
|
+
}
|
|
19028
|
+
estimateEngine(type) {
|
|
19029
|
+
switch (type) {
|
|
19030
|
+
case "web_search":
|
|
19031
|
+
return "gemini";
|
|
19032
|
+
// Gemini is good for web search
|
|
19033
|
+
case "code_review":
|
|
19034
|
+
case "code_generation":
|
|
19035
|
+
return "claude";
|
|
19036
|
+
// Claude is good for code
|
|
19037
|
+
case "analysis":
|
|
19038
|
+
return "claude";
|
|
19039
|
+
// Claude is good for analysis
|
|
19040
|
+
case "custom":
|
|
19041
|
+
default:
|
|
19042
|
+
return "auto";
|
|
19043
|
+
}
|
|
19044
|
+
}
|
|
19045
|
+
rowToTask(row) {
|
|
19046
|
+
let payload;
|
|
19047
|
+
try {
|
|
19048
|
+
if (row.is_compressed) {
|
|
19049
|
+
payload = decompressPayload(row.payload_compressed);
|
|
19050
|
+
} else {
|
|
19051
|
+
payload = JSON.parse(row.payload_compressed.toString("utf-8"));
|
|
19052
|
+
}
|
|
19053
|
+
} catch (error) {
|
|
19054
|
+
logger.error("Failed to decompress task payload", {
|
|
19055
|
+
taskId: row.id,
|
|
19056
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19057
|
+
});
|
|
19058
|
+
throw new TaskEngineError(
|
|
19059
|
+
`Failed to decompress task payload for task ${row.id}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
19060
|
+
"STORE_ERROR",
|
|
19061
|
+
{ taskId: row.id, originalError: error }
|
|
19062
|
+
);
|
|
19063
|
+
}
|
|
19064
|
+
let result = null;
|
|
19065
|
+
if (row.result_compressed) {
|
|
19066
|
+
try {
|
|
19067
|
+
if (row.result_is_compressed) {
|
|
19068
|
+
result = decompressPayload(row.result_compressed);
|
|
19069
|
+
} else {
|
|
19070
|
+
result = JSON.parse(row.result_compressed.toString("utf-8"));
|
|
19071
|
+
}
|
|
19072
|
+
} catch (error) {
|
|
19073
|
+
logger.warn("Failed to decompress task result, returning null", {
|
|
19074
|
+
taskId: row.id,
|
|
19075
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19076
|
+
});
|
|
19077
|
+
result = null;
|
|
19078
|
+
}
|
|
19079
|
+
}
|
|
19080
|
+
return {
|
|
19081
|
+
id: row.id,
|
|
19082
|
+
type: row.type,
|
|
19083
|
+
status: row.status,
|
|
19084
|
+
engine: row.engine,
|
|
19085
|
+
priority: row.priority,
|
|
19086
|
+
payload,
|
|
19087
|
+
payloadSize: row.payload_size_bytes,
|
|
19088
|
+
result,
|
|
19089
|
+
context: {
|
|
19090
|
+
originClient: row.origin_client,
|
|
19091
|
+
callChain: JSON.parse(row.call_chain),
|
|
19092
|
+
depth: row.depth
|
|
19093
|
+
},
|
|
19094
|
+
createdAt: row.created_at,
|
|
19095
|
+
startedAt: row.started_at,
|
|
19096
|
+
completedAt: row.completed_at,
|
|
19097
|
+
expiresAt: row.expires_at,
|
|
19098
|
+
metrics: row.duration_ms != null ? {
|
|
19099
|
+
durationMs: row.duration_ms,
|
|
19100
|
+
tokensPrompt: row.tokens_prompt,
|
|
19101
|
+
tokensCompletion: row.tokens_completion
|
|
19102
|
+
} : null,
|
|
19103
|
+
error: row.error_code ? {
|
|
19104
|
+
code: row.error_code,
|
|
19105
|
+
message: row.error_message ?? ""
|
|
19106
|
+
} : null,
|
|
19107
|
+
retryCount: row.retry_count
|
|
19108
|
+
};
|
|
19109
|
+
}
|
|
19110
|
+
};
|
|
19111
|
+
function createTaskStore(config) {
|
|
19112
|
+
return new TaskStore(config);
|
|
19113
|
+
}
|
|
19114
|
+
|
|
19115
|
+
// src/core/task-engine/engine.ts
|
|
19116
|
+
init_esm_shims();
|
|
19117
|
+
init_logger();
|
|
19118
|
+
var DEFAULT_CONFIG4 = {
|
|
19119
|
+
store: {},
|
|
19120
|
+
loopGuard: {},
|
|
19121
|
+
maxConcurrent: 50,
|
|
19122
|
+
// Phase 5: Increased from 36 to 50
|
|
19123
|
+
defaultTimeoutMs: 12e4,
|
|
19124
|
+
maxRetries: 3,
|
|
19125
|
+
retryDelayMs: 1e3,
|
|
19126
|
+
cacheEnabled: true,
|
|
19127
|
+
cacheTtlMs: 36e5
|
|
19128
|
+
// 1 hour
|
|
19129
|
+
};
|
|
19130
|
+
var TaskEngine = class extends EventEmitter {
|
|
19131
|
+
config;
|
|
19132
|
+
loopGuard;
|
|
19133
|
+
store;
|
|
19134
|
+
runningTasks = /* @__PURE__ */ new Map();
|
|
19135
|
+
closed = false;
|
|
19136
|
+
// Statistics
|
|
19137
|
+
stats = {
|
|
19138
|
+
totalCreated: 0,
|
|
19139
|
+
completed: 0,
|
|
19140
|
+
failed: 0,
|
|
19141
|
+
expired: 0,
|
|
19142
|
+
cacheHits: 0,
|
|
19143
|
+
cacheMisses: 0,
|
|
19144
|
+
totalDurationMs: 0
|
|
19145
|
+
};
|
|
19146
|
+
constructor(config = {}) {
|
|
19147
|
+
super();
|
|
19148
|
+
this.config = {
|
|
19149
|
+
...DEFAULT_CONFIG4,
|
|
19150
|
+
...config,
|
|
19151
|
+
store: { ...DEFAULT_CONFIG4.store, ...config.store },
|
|
19152
|
+
loopGuard: { ...DEFAULT_CONFIG4.loopGuard, ...config.loopGuard }
|
|
19153
|
+
};
|
|
19154
|
+
this.loopGuard = createLoopGuard(this.config.loopGuard);
|
|
19155
|
+
this.store = createTaskStore(this.config.store);
|
|
19156
|
+
logger.info("TaskEngine initialized", {
|
|
19157
|
+
maxConcurrent: this.config.maxConcurrent,
|
|
19158
|
+
defaultTimeoutMs: this.config.defaultTimeoutMs,
|
|
19159
|
+
cacheEnabled: this.config.cacheEnabled
|
|
19160
|
+
});
|
|
19161
|
+
}
|
|
19162
|
+
// ============================================================================
|
|
19163
|
+
// Public API
|
|
19164
|
+
// ============================================================================
|
|
19165
|
+
/**
|
|
19166
|
+
* Create a new task
|
|
19167
|
+
*/
|
|
19168
|
+
async createTask(input) {
|
|
19169
|
+
this.ensureOpen();
|
|
19170
|
+
try {
|
|
19171
|
+
const result = this.store.createTask(input);
|
|
19172
|
+
this.stats.totalCreated++;
|
|
19173
|
+
this.emit("task:created", result);
|
|
19174
|
+
logger.debug("Task created via engine", { taskId: result.id });
|
|
19175
|
+
return result;
|
|
19176
|
+
} catch (error) {
|
|
19177
|
+
if (error instanceof TaskEngineError) {
|
|
19178
|
+
throw error;
|
|
19179
|
+
}
|
|
19180
|
+
throw new TaskEngineError(
|
|
19181
|
+
`Failed to create task: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
19182
|
+
"STORE_ERROR",
|
|
19183
|
+
{ originalError: error }
|
|
19184
|
+
);
|
|
19185
|
+
}
|
|
19186
|
+
}
|
|
19187
|
+
/**
|
|
19188
|
+
* Run a task
|
|
19189
|
+
*/
|
|
19190
|
+
async runTask(taskId, options = {}) {
|
|
19191
|
+
this.ensureOpen();
|
|
19192
|
+
if (this.runningTasks.size >= this.config.maxConcurrent) {
|
|
19193
|
+
throw new TaskEngineError(
|
|
19194
|
+
`Maximum concurrent tasks reached: ${this.config.maxConcurrent}`,
|
|
19195
|
+
"EXECUTION_FAILED",
|
|
19196
|
+
{ runningCount: this.runningTasks.size, limit: this.config.maxConcurrent }
|
|
19197
|
+
);
|
|
19198
|
+
}
|
|
19199
|
+
const task = this.store.getTask(taskId);
|
|
19200
|
+
if (!task) {
|
|
19201
|
+
throw new TaskEngineError(`Task not found: ${taskId}`, "TASK_NOT_FOUND");
|
|
19202
|
+
}
|
|
19203
|
+
if (task.status === "running") {
|
|
19204
|
+
throw new TaskEngineError(
|
|
19205
|
+
`Task is already running: ${taskId}`,
|
|
19206
|
+
"TASK_ALREADY_RUNNING"
|
|
19207
|
+
);
|
|
19208
|
+
}
|
|
19209
|
+
if (task.status === "completed") {
|
|
19210
|
+
if (task.result) {
|
|
19211
|
+
this.stats.cacheHits++;
|
|
19212
|
+
return {
|
|
19213
|
+
taskId,
|
|
19214
|
+
status: "completed",
|
|
19215
|
+
result: task.result,
|
|
19216
|
+
engine: task.engine ?? "auto",
|
|
19217
|
+
metrics: task.metrics,
|
|
19218
|
+
error: null,
|
|
19219
|
+
cacheHit: true
|
|
19220
|
+
};
|
|
19221
|
+
}
|
|
19222
|
+
}
|
|
19223
|
+
if (task.status === "expired") {
|
|
19224
|
+
throw new TaskEngineError(`Task has expired: ${taskId}`, "TASK_EXPIRED");
|
|
19225
|
+
}
|
|
19226
|
+
if (Date.now() > task.expiresAt) {
|
|
19227
|
+
this.store.updateTaskStatus(taskId, "expired");
|
|
19228
|
+
throw new TaskEngineError(`Task has expired: ${taskId}`, "TASK_EXPIRED");
|
|
19229
|
+
}
|
|
19230
|
+
const targetEngine = options.engineOverride ?? task.engine ?? this.estimateEngine(task.type);
|
|
19231
|
+
const loopContext = this.buildLoopContext(task, options.loopContext);
|
|
19232
|
+
try {
|
|
19233
|
+
this.loopGuard.validateExecution(loopContext, targetEngine);
|
|
19234
|
+
} catch (error) {
|
|
19235
|
+
if (isLoopPreventionError(error)) {
|
|
19236
|
+
this.emit("loop:prevented", taskId, loopContext, targetEngine);
|
|
19237
|
+
logger.warn("Loop prevented", {
|
|
19238
|
+
taskId,
|
|
19239
|
+
targetEngine,
|
|
19240
|
+
callChain: error.callChain
|
|
19241
|
+
});
|
|
19242
|
+
}
|
|
19243
|
+
throw error;
|
|
19244
|
+
}
|
|
19245
|
+
return this.executeTask(task, targetEngine, loopContext, options);
|
|
19246
|
+
}
|
|
19247
|
+
/**
|
|
19248
|
+
* Get task result (without running)
|
|
19249
|
+
*/
|
|
19250
|
+
getTaskResult(taskId) {
|
|
19251
|
+
this.ensureOpen();
|
|
19252
|
+
const task = this.store.getTask(taskId);
|
|
19253
|
+
if (!task) {
|
|
19254
|
+
return null;
|
|
19255
|
+
}
|
|
19256
|
+
if (task.status !== "completed") {
|
|
19257
|
+
return null;
|
|
19258
|
+
}
|
|
19259
|
+
return {
|
|
19260
|
+
taskId,
|
|
19261
|
+
status: "completed",
|
|
19262
|
+
result: task.result,
|
|
19263
|
+
engine: task.engine ?? "auto",
|
|
19264
|
+
metrics: task.metrics,
|
|
19265
|
+
error: null,
|
|
19266
|
+
cacheHit: true
|
|
19267
|
+
};
|
|
19268
|
+
}
|
|
19269
|
+
/**
|
|
19270
|
+
* Get a task by ID
|
|
19271
|
+
*/
|
|
19272
|
+
getTask(taskId) {
|
|
19273
|
+
this.ensureOpen();
|
|
19274
|
+
return this.store.getTask(taskId);
|
|
19275
|
+
}
|
|
19276
|
+
/**
|
|
19277
|
+
* List tasks with pagination info
|
|
19278
|
+
*/
|
|
19279
|
+
listTasks(filter) {
|
|
19280
|
+
this.ensureOpen();
|
|
19281
|
+
const tasks = this.store.listTasks(filter);
|
|
19282
|
+
const total = this.store.countTasks(filter);
|
|
19283
|
+
return { tasks, total };
|
|
19284
|
+
}
|
|
19285
|
+
/**
|
|
19286
|
+
* Delete a task
|
|
19287
|
+
*/
|
|
19288
|
+
deleteTask(taskId, force = false) {
|
|
19289
|
+
this.ensureOpen();
|
|
19290
|
+
const task = this.store.getTask(taskId);
|
|
19291
|
+
if (!task) {
|
|
19292
|
+
return false;
|
|
19293
|
+
}
|
|
19294
|
+
if (task.status === "running" && !force) {
|
|
19295
|
+
throw new TaskEngineError(
|
|
19296
|
+
`Cannot delete running task: ${taskId}. Use force=true to override.`,
|
|
19297
|
+
"TASK_ALREADY_RUNNING"
|
|
19298
|
+
);
|
|
19299
|
+
}
|
|
19300
|
+
if (task.status === "running") {
|
|
19301
|
+
const controller = this.runningTasks.get(taskId);
|
|
19302
|
+
if (controller) {
|
|
19303
|
+
controller.abort();
|
|
19304
|
+
this.runningTasks.delete(taskId);
|
|
19305
|
+
}
|
|
19306
|
+
}
|
|
19307
|
+
return this.store.deleteTask(taskId);
|
|
19308
|
+
}
|
|
19309
|
+
/**
|
|
19310
|
+
* Get engine statistics
|
|
19311
|
+
*/
|
|
19312
|
+
getStats() {
|
|
19313
|
+
this.ensureOpen();
|
|
19314
|
+
const storeStats = this.store.getStats();
|
|
19315
|
+
const cacheTotal = this.stats.cacheHits + this.stats.cacheMisses;
|
|
19316
|
+
return {
|
|
19317
|
+
totalCreated: this.stats.totalCreated,
|
|
19318
|
+
runningCount: this.runningTasks.size,
|
|
19319
|
+
completedCount: storeStats.byStatus.completed,
|
|
19320
|
+
failedCount: storeStats.byStatus.failed,
|
|
19321
|
+
expiredCount: storeStats.byStatus.expired,
|
|
19322
|
+
cache: {
|
|
19323
|
+
hits: this.stats.cacheHits,
|
|
19324
|
+
misses: this.stats.cacheMisses,
|
|
19325
|
+
hitRate: cacheTotal > 0 ? this.stats.cacheHits / cacheTotal : 0
|
|
19326
|
+
},
|
|
19327
|
+
avgDurationMs: this.stats.completed > 0 ? this.stats.totalDurationMs / this.stats.completed : 0
|
|
19328
|
+
};
|
|
19329
|
+
}
|
|
19330
|
+
/**
|
|
19331
|
+
* Cleanup expired tasks
|
|
19332
|
+
*/
|
|
19333
|
+
cleanupExpired() {
|
|
19334
|
+
this.ensureOpen();
|
|
19335
|
+
const cleaned = this.store.cleanupExpired();
|
|
19336
|
+
this.stats.expired += cleaned;
|
|
19337
|
+
return cleaned;
|
|
19338
|
+
}
|
|
19339
|
+
/**
|
|
19340
|
+
* Shutdown the engine
|
|
19341
|
+
*/
|
|
19342
|
+
async shutdown() {
|
|
19343
|
+
if (this.closed) return;
|
|
19344
|
+
logger.info("TaskEngine shutting down", {
|
|
19345
|
+
runningTasks: this.runningTasks.size
|
|
19346
|
+
});
|
|
19347
|
+
for (const [taskId, controller] of this.runningTasks) {
|
|
19348
|
+
controller.abort();
|
|
19349
|
+
logger.debug("Cancelled running task", { taskId });
|
|
19350
|
+
}
|
|
19351
|
+
this.runningTasks.clear();
|
|
19352
|
+
this.store.close();
|
|
19353
|
+
this.closed = true;
|
|
19354
|
+
logger.info("TaskEngine shutdown complete");
|
|
19355
|
+
}
|
|
19356
|
+
/**
|
|
19357
|
+
* Check if engine is healthy
|
|
19358
|
+
*/
|
|
19359
|
+
isHealthy() {
|
|
19360
|
+
return !this.closed && this.runningTasks.size < this.config.maxConcurrent;
|
|
19361
|
+
}
|
|
19362
|
+
// ============================================================================
|
|
19363
|
+
// Private Methods
|
|
19364
|
+
// ============================================================================
|
|
19365
|
+
async executeTask(task, targetEngine, context, options) {
|
|
19366
|
+
const taskId = task.id;
|
|
19367
|
+
const abortController = new AbortController();
|
|
19368
|
+
const startTime = Date.now();
|
|
19369
|
+
this.runningTasks.set(taskId, abortController);
|
|
19370
|
+
this.store.updateTaskStatus(taskId, "running");
|
|
19371
|
+
this.emit("task:started", taskId, targetEngine);
|
|
19372
|
+
const timeoutMs = options.timeoutMs ?? this.config.defaultTimeoutMs;
|
|
19373
|
+
const timeoutId = setTimeout(() => {
|
|
19374
|
+
abortController.abort();
|
|
19375
|
+
}, timeoutMs);
|
|
19376
|
+
try {
|
|
19377
|
+
const result = await this.executeWithRetry(
|
|
19378
|
+
task,
|
|
19379
|
+
targetEngine,
|
|
19380
|
+
context,
|
|
19381
|
+
abortController.signal
|
|
19382
|
+
);
|
|
19383
|
+
const durationMs = Date.now() - startTime;
|
|
19384
|
+
this.store.updateTaskResult(taskId, result, {
|
|
19385
|
+
durationMs,
|
|
19386
|
+
tokensPrompt: null,
|
|
19387
|
+
tokensCompletion: null
|
|
19388
|
+
});
|
|
19389
|
+
this.stats.completed++;
|
|
19390
|
+
this.stats.cacheMisses++;
|
|
19391
|
+
this.stats.totalDurationMs += durationMs;
|
|
19392
|
+
const taskResult = {
|
|
19393
|
+
taskId,
|
|
19394
|
+
status: "completed",
|
|
19395
|
+
result,
|
|
19396
|
+
engine: targetEngine,
|
|
19397
|
+
metrics: { durationMs, tokensPrompt: null, tokensCompletion: null },
|
|
19398
|
+
error: null,
|
|
19399
|
+
cacheHit: false
|
|
19400
|
+
};
|
|
19401
|
+
this.emit("task:completed", taskId, taskResult);
|
|
19402
|
+
logger.debug("Task completed", { taskId, durationMs, engine: targetEngine });
|
|
19403
|
+
return taskResult;
|
|
19404
|
+
} catch (error) {
|
|
19405
|
+
const durationMs = Date.now() - startTime;
|
|
19406
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
19407
|
+
if (abortController.signal.aborted) {
|
|
19408
|
+
const taskError2 = { code: "TIMEOUT", message: `Task timed out after ${timeoutMs}ms` };
|
|
19409
|
+
this.store.updateTaskFailed(taskId, taskError2, durationMs);
|
|
19410
|
+
this.stats.failed++;
|
|
19411
|
+
this.emit("task:failed", taskId, new TaskEngineError(taskError2.message, "EXECUTION_TIMEOUT"));
|
|
19412
|
+
return {
|
|
19413
|
+
taskId,
|
|
19414
|
+
status: "failed",
|
|
19415
|
+
result: null,
|
|
19416
|
+
engine: targetEngine,
|
|
19417
|
+
metrics: { durationMs, tokensPrompt: null, tokensCompletion: null },
|
|
19418
|
+
error: taskError2,
|
|
19419
|
+
cacheHit: false
|
|
19420
|
+
};
|
|
19421
|
+
}
|
|
19422
|
+
const taskError = {
|
|
19423
|
+
code: error instanceof TaskEngineError ? error.code : "EXECUTION_FAILED",
|
|
19424
|
+
message: errorObj.message
|
|
19425
|
+
};
|
|
19426
|
+
this.store.updateTaskFailed(taskId, taskError, durationMs);
|
|
19427
|
+
this.stats.failed++;
|
|
19428
|
+
this.emit("task:failed", taskId, errorObj);
|
|
19429
|
+
logger.error("Task failed", { taskId, error: errorObj.message, durationMs });
|
|
19430
|
+
return {
|
|
19431
|
+
taskId,
|
|
19432
|
+
status: "failed",
|
|
19433
|
+
result: null,
|
|
19434
|
+
engine: targetEngine,
|
|
19435
|
+
metrics: { durationMs, tokensPrompt: null, tokensCompletion: null },
|
|
19436
|
+
error: taskError,
|
|
19437
|
+
cacheHit: false
|
|
19438
|
+
};
|
|
19439
|
+
} finally {
|
|
19440
|
+
clearTimeout(timeoutId);
|
|
19441
|
+
this.runningTasks.delete(taskId);
|
|
19442
|
+
}
|
|
19443
|
+
}
|
|
19444
|
+
async executeWithRetry(task, engine, context, signal) {
|
|
19445
|
+
let lastError;
|
|
19446
|
+
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
19447
|
+
if (signal.aborted) {
|
|
19448
|
+
throw new TaskEngineError("Task execution aborted", "EXECUTION_TIMEOUT");
|
|
19449
|
+
}
|
|
19450
|
+
try {
|
|
19451
|
+
return await this.executeOnEngine(task, engine, context, signal);
|
|
19452
|
+
} catch (error) {
|
|
19453
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
19454
|
+
if (isLoopPreventionError(error)) {
|
|
19455
|
+
throw error;
|
|
19456
|
+
}
|
|
19457
|
+
if (signal.aborted) {
|
|
19458
|
+
throw error;
|
|
19459
|
+
}
|
|
19460
|
+
if (attempt < this.config.maxRetries && this.isRetryableError(error)) {
|
|
19461
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
19462
|
+
this.store.incrementRetry(task.id);
|
|
19463
|
+
this.emit("task:retry", task.id, attempt + 1, lastError);
|
|
19464
|
+
logger.debug("Retrying task", {
|
|
19465
|
+
taskId: task.id,
|
|
19466
|
+
attempt: attempt + 1,
|
|
19467
|
+
delay,
|
|
19468
|
+
error: lastError.message
|
|
19469
|
+
});
|
|
19470
|
+
await this.sleep(delay, signal);
|
|
19471
|
+
continue;
|
|
19472
|
+
}
|
|
19473
|
+
throw error;
|
|
19474
|
+
}
|
|
19475
|
+
}
|
|
19476
|
+
throw lastError ?? new TaskEngineError("Task execution failed", "EXECUTION_FAILED");
|
|
19477
|
+
}
|
|
19478
|
+
/**
|
|
19479
|
+
* Execute task on target engine
|
|
19480
|
+
*
|
|
19481
|
+
* Phase 1: Simulated execution (returns mock result)
|
|
19482
|
+
* Phase 2+: Will integrate with Router for actual execution
|
|
19483
|
+
*/
|
|
19484
|
+
async executeOnEngine(task, engine, context, signal) {
|
|
19485
|
+
logger.debug("Executing task on engine", {
|
|
19486
|
+
taskId: task.id,
|
|
19487
|
+
engine,
|
|
19488
|
+
type: task.type,
|
|
19489
|
+
callChain: context.callChain
|
|
19490
|
+
});
|
|
19491
|
+
await this.sleep(100, signal);
|
|
19492
|
+
switch (task.type) {
|
|
19493
|
+
case "web_search":
|
|
19494
|
+
return {
|
|
19495
|
+
query: task.payload.query ?? "unknown",
|
|
19496
|
+
results: [
|
|
19497
|
+
{ title: "Result 1", url: "https://example.com/1", snippet: "Sample result 1" },
|
|
19498
|
+
{ title: "Result 2", url: "https://example.com/2", snippet: "Sample result 2" }
|
|
19499
|
+
],
|
|
19500
|
+
engine,
|
|
19501
|
+
timestamp: Date.now()
|
|
19502
|
+
};
|
|
19503
|
+
case "code_review":
|
|
19504
|
+
return {
|
|
19505
|
+
file: task.payload.file ?? "unknown.ts",
|
|
19506
|
+
issues: [],
|
|
19507
|
+
suggestions: ["Consider adding type annotations"],
|
|
19508
|
+
engine,
|
|
19509
|
+
timestamp: Date.now()
|
|
19510
|
+
};
|
|
19511
|
+
case "code_generation":
|
|
19512
|
+
return {
|
|
19513
|
+
prompt: task.payload.prompt ?? "",
|
|
19514
|
+
code: "// Generated code placeholder",
|
|
19515
|
+
language: task.payload.language ?? "typescript",
|
|
19516
|
+
engine,
|
|
19517
|
+
timestamp: Date.now()
|
|
19518
|
+
};
|
|
19519
|
+
case "analysis":
|
|
19520
|
+
return {
|
|
19521
|
+
input: task.payload.input ?? {},
|
|
19522
|
+
analysis: "Analysis result placeholder",
|
|
19523
|
+
confidence: 0.95,
|
|
19524
|
+
engine,
|
|
19525
|
+
timestamp: Date.now()
|
|
19526
|
+
};
|
|
19527
|
+
case "custom":
|
|
19528
|
+
default:
|
|
19529
|
+
return {
|
|
19530
|
+
payload: task.payload,
|
|
19531
|
+
result: "Custom task completed",
|
|
19532
|
+
engine,
|
|
19533
|
+
timestamp: Date.now()
|
|
19534
|
+
};
|
|
19535
|
+
}
|
|
19536
|
+
}
|
|
19537
|
+
buildLoopContext(task, incomingContext) {
|
|
19538
|
+
if (incomingContext) {
|
|
19539
|
+
return this.loopGuard.mergeContext(incomingContext);
|
|
19540
|
+
}
|
|
19541
|
+
return {
|
|
19542
|
+
taskId: task.id,
|
|
19543
|
+
originClient: task.context.originClient,
|
|
19544
|
+
callChain: task.context.callChain.length > 0 ? task.context.callChain : [task.context.originClient],
|
|
19545
|
+
depth: task.context.depth,
|
|
19546
|
+
maxDepth: this.loopGuard.getConfig().maxDepth,
|
|
19547
|
+
createdAt: task.createdAt
|
|
19548
|
+
};
|
|
19549
|
+
}
|
|
19550
|
+
estimateEngine(type) {
|
|
19551
|
+
switch (type) {
|
|
19552
|
+
case "web_search":
|
|
19553
|
+
return "gemini";
|
|
19554
|
+
case "code_review":
|
|
19555
|
+
case "code_generation":
|
|
19556
|
+
return "claude";
|
|
19557
|
+
case "analysis":
|
|
19558
|
+
return "claude";
|
|
19559
|
+
default:
|
|
19560
|
+
return "claude";
|
|
19561
|
+
}
|
|
19562
|
+
}
|
|
19563
|
+
isRetryableError(error) {
|
|
19564
|
+
if (error instanceof TaskEngineError) {
|
|
19565
|
+
const nonRetryable = [
|
|
19566
|
+
"TASK_NOT_FOUND",
|
|
19567
|
+
"TASK_EXPIRED",
|
|
19568
|
+
"PAYLOAD_TOO_LARGE",
|
|
19569
|
+
"LOOP_DETECTED",
|
|
19570
|
+
"DEPTH_EXCEEDED",
|
|
19571
|
+
"CHAIN_TOO_LONG",
|
|
19572
|
+
"BLOCKED_PATTERN"
|
|
19573
|
+
];
|
|
19574
|
+
return !nonRetryable.includes(error.code);
|
|
19575
|
+
}
|
|
19576
|
+
return true;
|
|
19577
|
+
}
|
|
19578
|
+
ensureOpen() {
|
|
19579
|
+
if (this.closed) {
|
|
19580
|
+
throw new TaskEngineError("TaskEngine is closed", "EXECUTION_FAILED");
|
|
19581
|
+
}
|
|
19582
|
+
}
|
|
19583
|
+
sleep(ms, signal) {
|
|
19584
|
+
return new Promise((resolve5, reject) => {
|
|
19585
|
+
if (signal?.aborted) {
|
|
19586
|
+
reject(new Error("Aborted"));
|
|
19587
|
+
return;
|
|
19588
|
+
}
|
|
19589
|
+
const abortHandler = () => {
|
|
19590
|
+
clearTimeout(timeoutId);
|
|
19591
|
+
reject(new Error("Aborted"));
|
|
19592
|
+
};
|
|
19593
|
+
const timeoutId = setTimeout(() => {
|
|
19594
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
19595
|
+
resolve5();
|
|
19596
|
+
}, ms);
|
|
19597
|
+
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
19598
|
+
});
|
|
19599
|
+
}
|
|
19600
|
+
};
|
|
19601
|
+
var defaultTaskEngine = null;
|
|
19602
|
+
function getTaskEngine(config) {
|
|
19603
|
+
if (!defaultTaskEngine) {
|
|
19604
|
+
defaultTaskEngine = new TaskEngine(config);
|
|
19605
|
+
}
|
|
19606
|
+
return defaultTaskEngine;
|
|
19607
|
+
}
|
|
19608
|
+
|
|
19609
|
+
// src/core/task-engine/cache.ts
|
|
19610
|
+
init_esm_shims();
|
|
19611
|
+
init_logger();
|
|
19612
|
+
|
|
19613
|
+
// src/core/task-engine/write-batcher.ts
|
|
19614
|
+
init_esm_shims();
|
|
19615
|
+
init_logger();
|
|
19616
|
+
|
|
19617
|
+
// src/core/task-engine/request-coalescer.ts
|
|
19618
|
+
init_esm_shims();
|
|
19619
|
+
init_logger();
|
|
19620
|
+
|
|
19621
|
+
// src/core/task-engine/rate-limiter.ts
|
|
19622
|
+
init_esm_shims();
|
|
19623
|
+
init_logger();
|
|
19624
|
+
|
|
19625
|
+
// src/core/task-engine/audit-logger.ts
|
|
19626
|
+
init_esm_shims();
|
|
19627
|
+
init_logger();
|
|
19628
|
+
|
|
19629
|
+
// src/core/task-engine/circuit-breaker.ts
|
|
19630
|
+
init_esm_shims();
|
|
19631
|
+
init_logger();
|
|
19632
|
+
|
|
19633
|
+
// src/core/task-engine/write-pool.ts
|
|
19634
|
+
init_esm_shims();
|
|
19635
|
+
init_logger();
|
|
19636
|
+
|
|
19637
|
+
// src/core/task-engine/task-queue.ts
|
|
19638
|
+
init_esm_shims();
|
|
19639
|
+
init_logger();
|
|
19640
|
+
|
|
19641
|
+
// src/mcp/tools/task/create-task.ts
|
|
19642
|
+
init_logger();
|
|
19643
|
+
function createCreateTaskHandler(deps) {
|
|
19644
|
+
return async (input) => {
|
|
19645
|
+
const startTime = Date.now();
|
|
19646
|
+
logger.info("[create_task] Creating task", {
|
|
19647
|
+
type: input.type,
|
|
19648
|
+
engine: input.engine ?? "auto",
|
|
19649
|
+
priority: input.priority ?? 5,
|
|
19650
|
+
payloadSize: JSON.stringify(input.payload).length
|
|
19651
|
+
});
|
|
19652
|
+
try {
|
|
19653
|
+
const taskEngine = getTaskEngine();
|
|
19654
|
+
const session = deps.getSession();
|
|
19655
|
+
const taskInput = {
|
|
19656
|
+
type: input.type,
|
|
19657
|
+
payload: input.payload,
|
|
19658
|
+
engine: input.engine,
|
|
19659
|
+
priority: input.priority,
|
|
19660
|
+
ttlHours: input.ttl_hours,
|
|
19661
|
+
context: session ? {
|
|
19662
|
+
originClient: mapNormalizedProviderToOriginClient(session.normalizedProvider),
|
|
19663
|
+
callChain: [mapNormalizedProviderToOriginClient(session.normalizedProvider)],
|
|
19664
|
+
depth: 0
|
|
19665
|
+
} : void 0
|
|
19666
|
+
};
|
|
19667
|
+
const result = await taskEngine.createTask(taskInput);
|
|
19668
|
+
const output = {
|
|
19669
|
+
task_id: result.id,
|
|
19670
|
+
status: "pending",
|
|
19671
|
+
estimated_engine: result.estimatedEngine,
|
|
19672
|
+
expires_at: new Date(result.expiresAt).toISOString(),
|
|
19673
|
+
payload_size_bytes: result.payloadSize,
|
|
19674
|
+
compression_ratio: result.compressionRatio
|
|
19675
|
+
};
|
|
19676
|
+
logger.info("[create_task] Task created successfully", {
|
|
19677
|
+
taskId: result.id,
|
|
19678
|
+
estimatedEngine: result.estimatedEngine,
|
|
19679
|
+
durationMs: Date.now() - startTime
|
|
19680
|
+
});
|
|
19681
|
+
return output;
|
|
19682
|
+
} catch (error) {
|
|
19683
|
+
logger.error("[create_task] Failed to create task", {
|
|
19684
|
+
error: error instanceof Error ? error.message : String(error),
|
|
19685
|
+
type: input.type
|
|
19686
|
+
});
|
|
19687
|
+
throw error;
|
|
19688
|
+
}
|
|
19689
|
+
};
|
|
19690
|
+
}
|
|
19691
|
+
function mapNormalizedProviderToOriginClient(provider) {
|
|
19692
|
+
const mapping = {
|
|
19693
|
+
"claude": "claude-code",
|
|
19694
|
+
"gemini": "gemini-cli",
|
|
19695
|
+
"codex": "codex-cli",
|
|
19696
|
+
"ax-cli": "ax-cli",
|
|
19697
|
+
"unknown": "unknown"
|
|
19698
|
+
};
|
|
19699
|
+
return mapping[provider] ?? "unknown";
|
|
19700
|
+
}
|
|
19701
|
+
var createTaskSchema = {
|
|
19702
|
+
name: "create_task",
|
|
19703
|
+
description: "Create a new task with payload for deferred execution. Returns task_id for later execution via run_task.",
|
|
19704
|
+
inputSchema: {
|
|
19705
|
+
type: "object",
|
|
19706
|
+
properties: {
|
|
19707
|
+
type: {
|
|
19708
|
+
type: "string",
|
|
19709
|
+
enum: ["web_search", "code_review", "code_generation", "analysis", "custom"],
|
|
19710
|
+
description: "Task type for routing optimization"
|
|
19711
|
+
},
|
|
19712
|
+
payload: {
|
|
19713
|
+
type: "object",
|
|
19714
|
+
description: "Task data (max 1MB after JSON serialization)"
|
|
19715
|
+
},
|
|
19716
|
+
engine: {
|
|
19717
|
+
type: "string",
|
|
19718
|
+
enum: ["auto", "gemini", "claude", "codex", "ax-cli"],
|
|
19719
|
+
default: "auto",
|
|
19720
|
+
description: "Target engine (auto = router decides)"
|
|
19721
|
+
},
|
|
19722
|
+
priority: {
|
|
19723
|
+
type: "integer",
|
|
19724
|
+
minimum: 1,
|
|
19725
|
+
maximum: 10,
|
|
19726
|
+
default: 5,
|
|
19727
|
+
description: "Execution priority (1=lowest, 10=highest)"
|
|
19728
|
+
},
|
|
19729
|
+
ttl_hours: {
|
|
19730
|
+
type: "integer",
|
|
19731
|
+
minimum: 1,
|
|
19732
|
+
maximum: 168,
|
|
19733
|
+
default: 24,
|
|
19734
|
+
description: "Task time-to-live in hours"
|
|
19735
|
+
}
|
|
19736
|
+
},
|
|
19737
|
+
required: ["type", "payload"]
|
|
19738
|
+
}
|
|
19739
|
+
};
|
|
19740
|
+
|
|
19741
|
+
// src/mcp/tools/task/run-task.ts
|
|
19742
|
+
init_esm_shims();
|
|
19743
|
+
init_logger();
|
|
19744
|
+
function createRunTaskHandler(deps) {
|
|
19745
|
+
return async (input) => {
|
|
19746
|
+
const startTime = Date.now();
|
|
19747
|
+
logger.info("[run_task] Executing task", {
|
|
19748
|
+
taskId: input.task_id,
|
|
19749
|
+
engineOverride: input.engine_override,
|
|
19750
|
+
timeoutMs: input.timeout_ms,
|
|
19751
|
+
skipCache: input.skip_cache
|
|
19752
|
+
});
|
|
19753
|
+
try {
|
|
19754
|
+
const taskEngine = getTaskEngine();
|
|
19755
|
+
const session = deps.getSession();
|
|
19756
|
+
const options = {
|
|
19757
|
+
engineOverride: input.engine_override,
|
|
19758
|
+
timeoutMs: input.timeout_ms,
|
|
19759
|
+
skipCache: input.skip_cache
|
|
19760
|
+
};
|
|
19761
|
+
const result = await taskEngine.runTask(input.task_id, options);
|
|
19762
|
+
const output = {
|
|
19763
|
+
task_id: result.taskId,
|
|
19764
|
+
status: result.status,
|
|
19765
|
+
result: result.result,
|
|
19766
|
+
engine: result.engine,
|
|
19767
|
+
metrics: {
|
|
19768
|
+
duration_ms: result.metrics?.durationMs ?? 0,
|
|
19769
|
+
tokens_prompt: result.metrics?.tokensPrompt ?? null,
|
|
19770
|
+
tokens_completion: result.metrics?.tokensCompletion ?? null
|
|
19771
|
+
},
|
|
19772
|
+
cache_hit: result.cacheHit
|
|
19773
|
+
};
|
|
19774
|
+
if (result.error) {
|
|
19775
|
+
output.error = {
|
|
19776
|
+
code: result.error.code,
|
|
19777
|
+
message: result.error.message
|
|
19778
|
+
};
|
|
19779
|
+
}
|
|
19780
|
+
logger.info("[run_task] Task execution completed", {
|
|
19781
|
+
taskId: result.taskId,
|
|
19782
|
+
status: result.status,
|
|
19783
|
+
engine: result.engine,
|
|
19784
|
+
cacheHit: result.cacheHit,
|
|
19785
|
+
durationMs: Date.now() - startTime
|
|
19786
|
+
});
|
|
19787
|
+
return output;
|
|
19788
|
+
} catch (error) {
|
|
19789
|
+
logger.error("[run_task] Failed to execute task", {
|
|
19790
|
+
error: error instanceof Error ? error.message : String(error),
|
|
19791
|
+
taskId: input.task_id
|
|
19792
|
+
});
|
|
19793
|
+
throw error;
|
|
19794
|
+
}
|
|
19795
|
+
};
|
|
19796
|
+
}
|
|
19797
|
+
var runTaskSchema = {
|
|
19798
|
+
name: "run_task",
|
|
19799
|
+
description: "Execute a previously created task and return results. Blocks until completion or timeout.",
|
|
19800
|
+
inputSchema: {
|
|
19801
|
+
type: "object",
|
|
19802
|
+
properties: {
|
|
19803
|
+
task_id: {
|
|
19804
|
+
type: "string",
|
|
19805
|
+
description: "Task ID to execute"
|
|
19806
|
+
},
|
|
19807
|
+
engine_override: {
|
|
19808
|
+
type: "string",
|
|
19809
|
+
enum: ["gemini", "claude", "codex", "ax-cli"],
|
|
19810
|
+
description: "Override the estimated engine"
|
|
19811
|
+
},
|
|
19812
|
+
timeout_ms: {
|
|
19813
|
+
type: "integer",
|
|
19814
|
+
minimum: 1e3,
|
|
19815
|
+
maximum: 3e5,
|
|
19816
|
+
default: 3e4,
|
|
19817
|
+
description: "Custom timeout in milliseconds"
|
|
19818
|
+
},
|
|
19819
|
+
skip_cache: {
|
|
19820
|
+
type: "boolean",
|
|
19821
|
+
default: false,
|
|
19822
|
+
description: "Skip cache and force re-execution"
|
|
19823
|
+
}
|
|
19824
|
+
},
|
|
19825
|
+
required: ["task_id"]
|
|
19826
|
+
}
|
|
19827
|
+
};
|
|
19828
|
+
|
|
19829
|
+
// src/mcp/tools/task/get-task-result.ts
|
|
19830
|
+
init_esm_shims();
|
|
19831
|
+
init_logger();
|
|
19832
|
+
function createGetTaskResultHandler() {
|
|
19833
|
+
return async (input) => {
|
|
19834
|
+
logger.info("[get_task_result] Retrieving task", {
|
|
19835
|
+
taskId: input.task_id,
|
|
19836
|
+
includePayload: input.include_payload
|
|
19837
|
+
});
|
|
19838
|
+
try {
|
|
19839
|
+
const taskEngine = getTaskEngine();
|
|
19840
|
+
const task = taskEngine.getTask(input.task_id);
|
|
19841
|
+
if (!task) {
|
|
19842
|
+
throw new Error(`Task not found: ${input.task_id}`);
|
|
19843
|
+
}
|
|
19844
|
+
const output = {
|
|
19845
|
+
task_id: task.id,
|
|
19846
|
+
status: task.status,
|
|
19847
|
+
type: task.type,
|
|
19848
|
+
result: task.result,
|
|
19849
|
+
engine: task.engine,
|
|
19850
|
+
created_at: new Date(task.createdAt).toISOString(),
|
|
19851
|
+
completed_at: task.completedAt ? new Date(task.completedAt).toISOString() : null,
|
|
19852
|
+
expires_at: new Date(task.expiresAt).toISOString()
|
|
19853
|
+
};
|
|
19854
|
+
if (input.include_payload) {
|
|
19855
|
+
output.payload = task.payload;
|
|
19856
|
+
}
|
|
19857
|
+
if (task.error) {
|
|
19858
|
+
output.error = {
|
|
19859
|
+
code: task.error.code,
|
|
19860
|
+
message: task.error.message
|
|
19861
|
+
};
|
|
19862
|
+
}
|
|
19863
|
+
logger.info("[get_task_result] Task retrieved", {
|
|
19864
|
+
taskId: task.id,
|
|
19865
|
+
status: task.status
|
|
19866
|
+
});
|
|
19867
|
+
return output;
|
|
19868
|
+
} catch (error) {
|
|
19869
|
+
logger.error("[get_task_result] Failed to retrieve task", {
|
|
19870
|
+
error: error instanceof Error ? error.message : String(error),
|
|
19871
|
+
taskId: input.task_id
|
|
19872
|
+
});
|
|
19873
|
+
throw error;
|
|
19874
|
+
}
|
|
19875
|
+
};
|
|
19876
|
+
}
|
|
19877
|
+
var getTaskResultSchema = {
|
|
19878
|
+
name: "get_task_result",
|
|
19879
|
+
description: "Retrieve the result of a task. Does not execute - use run_task for execution.",
|
|
19880
|
+
inputSchema: {
|
|
19881
|
+
type: "object",
|
|
19882
|
+
properties: {
|
|
19883
|
+
task_id: {
|
|
19884
|
+
type: "string",
|
|
19885
|
+
description: "Task ID to retrieve"
|
|
19886
|
+
},
|
|
19887
|
+
include_payload: {
|
|
19888
|
+
type: "boolean",
|
|
19889
|
+
default: false,
|
|
19890
|
+
description: "Include original payload in response"
|
|
19891
|
+
}
|
|
19892
|
+
},
|
|
19893
|
+
required: ["task_id"]
|
|
19894
|
+
}
|
|
19895
|
+
};
|
|
19896
|
+
|
|
19897
|
+
// src/mcp/tools/task/list-tasks.ts
|
|
19898
|
+
init_esm_shims();
|
|
19899
|
+
init_logger();
|
|
19900
|
+
function createListTasksHandler() {
|
|
19901
|
+
return async (input) => {
|
|
19902
|
+
const limit = Math.min(input.limit ?? 20, 100);
|
|
19903
|
+
const offset = input.offset ?? 0;
|
|
19904
|
+
logger.info("[list_tasks] Listing tasks", {
|
|
19905
|
+
status: input.status,
|
|
19906
|
+
type: input.type,
|
|
19907
|
+
engine: input.engine,
|
|
19908
|
+
limit,
|
|
19909
|
+
offset
|
|
19910
|
+
});
|
|
19911
|
+
try {
|
|
19912
|
+
const taskEngine = getTaskEngine();
|
|
19913
|
+
const filter = {
|
|
19914
|
+
status: input.status,
|
|
19915
|
+
type: input.type,
|
|
19916
|
+
engine: input.engine,
|
|
19917
|
+
limit,
|
|
19918
|
+
offset
|
|
19919
|
+
};
|
|
19920
|
+
const result = taskEngine.listTasks(filter);
|
|
19921
|
+
const tasks = result.tasks.map((task) => ({
|
|
19922
|
+
task_id: task.id,
|
|
19923
|
+
type: task.type,
|
|
19924
|
+
status: task.status,
|
|
19925
|
+
engine: task.engine,
|
|
19926
|
+
priority: task.priority,
|
|
19927
|
+
created_at: new Date(task.createdAt).toISOString(),
|
|
19928
|
+
expires_at: new Date(task.expiresAt).toISOString(),
|
|
19929
|
+
has_result: task.result !== null
|
|
19930
|
+
}));
|
|
19931
|
+
const output = {
|
|
19932
|
+
tasks,
|
|
19933
|
+
total: result.total,
|
|
19934
|
+
offset,
|
|
19935
|
+
limit,
|
|
19936
|
+
has_more: offset + tasks.length < result.total
|
|
19937
|
+
};
|
|
19938
|
+
logger.info("[list_tasks] Tasks listed", {
|
|
19939
|
+
returned: tasks.length,
|
|
19940
|
+
total: result.total,
|
|
19941
|
+
hasMore: output.has_more
|
|
19942
|
+
});
|
|
19943
|
+
return output;
|
|
19944
|
+
} catch (error) {
|
|
19945
|
+
logger.error("[list_tasks] Failed to list tasks", {
|
|
19946
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19947
|
+
});
|
|
19948
|
+
throw error;
|
|
19949
|
+
}
|
|
19950
|
+
};
|
|
19951
|
+
}
|
|
19952
|
+
var listTasksSchema = {
|
|
19953
|
+
name: "list_tasks",
|
|
19954
|
+
description: "List tasks with optional filtering. Supports pagination.",
|
|
19955
|
+
inputSchema: {
|
|
19956
|
+
type: "object",
|
|
19957
|
+
properties: {
|
|
19958
|
+
status: {
|
|
19959
|
+
type: "string",
|
|
19960
|
+
enum: ["pending", "running", "completed", "failed", "expired"],
|
|
19961
|
+
description: "Filter by task status"
|
|
19962
|
+
},
|
|
19963
|
+
type: {
|
|
19964
|
+
type: "string",
|
|
19965
|
+
enum: ["web_search", "code_review", "code_generation", "analysis", "custom"],
|
|
19966
|
+
description: "Filter by task type"
|
|
19967
|
+
},
|
|
19968
|
+
engine: {
|
|
19969
|
+
type: "string",
|
|
19970
|
+
enum: ["gemini", "claude", "codex", "ax-cli"],
|
|
19971
|
+
description: "Filter by engine"
|
|
19972
|
+
},
|
|
19973
|
+
limit: {
|
|
19974
|
+
type: "integer",
|
|
19975
|
+
minimum: 1,
|
|
19976
|
+
maximum: 100,
|
|
19977
|
+
default: 20,
|
|
19978
|
+
description: "Maximum results to return"
|
|
19979
|
+
},
|
|
19980
|
+
offset: {
|
|
19981
|
+
type: "integer",
|
|
19982
|
+
minimum: 0,
|
|
19983
|
+
default: 0,
|
|
19984
|
+
description: "Offset for pagination"
|
|
19985
|
+
}
|
|
19986
|
+
},
|
|
19987
|
+
required: []
|
|
19988
|
+
}
|
|
19989
|
+
};
|
|
19990
|
+
|
|
19991
|
+
// src/mcp/tools/task/delete-task.ts
|
|
19992
|
+
init_esm_shims();
|
|
19993
|
+
init_logger();
|
|
19994
|
+
function createDeleteTaskHandler() {
|
|
19995
|
+
return async (input) => {
|
|
19996
|
+
logger.info("[delete_task] Deleting task", {
|
|
19997
|
+
taskId: input.task_id,
|
|
19998
|
+
force: input.force
|
|
19999
|
+
});
|
|
20000
|
+
try {
|
|
20001
|
+
const taskEngine = getTaskEngine();
|
|
20002
|
+
const task = taskEngine.getTask(input.task_id);
|
|
20003
|
+
if (!task) {
|
|
20004
|
+
return {
|
|
20005
|
+
task_id: input.task_id,
|
|
20006
|
+
deleted: false,
|
|
20007
|
+
previous_status: "unknown",
|
|
20008
|
+
message: `Task not found: ${input.task_id}`
|
|
20009
|
+
};
|
|
20010
|
+
}
|
|
20011
|
+
if (task.status === "running" && !input.force) {
|
|
20012
|
+
return {
|
|
20013
|
+
task_id: input.task_id,
|
|
20014
|
+
deleted: false,
|
|
20015
|
+
previous_status: task.status,
|
|
20016
|
+
message: "Cannot delete running task. Use force=true to override."
|
|
20017
|
+
};
|
|
20018
|
+
}
|
|
20019
|
+
const deleted = taskEngine.deleteTask(input.task_id);
|
|
20020
|
+
const output = {
|
|
20021
|
+
task_id: input.task_id,
|
|
20022
|
+
deleted,
|
|
20023
|
+
previous_status: task.status,
|
|
20024
|
+
message: deleted ? `Task ${input.task_id} deleted successfully` : `Failed to delete task ${input.task_id}`
|
|
20025
|
+
};
|
|
20026
|
+
logger.info("[delete_task] Task deletion result", {
|
|
20027
|
+
taskId: input.task_id,
|
|
20028
|
+
deleted,
|
|
20029
|
+
previousStatus: task.status
|
|
20030
|
+
});
|
|
20031
|
+
return output;
|
|
20032
|
+
} catch (error) {
|
|
20033
|
+
logger.error("[delete_task] Failed to delete task", {
|
|
20034
|
+
error: error instanceof Error ? error.message : String(error),
|
|
20035
|
+
taskId: input.task_id
|
|
20036
|
+
});
|
|
20037
|
+
throw error;
|
|
20038
|
+
}
|
|
20039
|
+
};
|
|
20040
|
+
}
|
|
20041
|
+
var deleteTaskSchema = {
|
|
20042
|
+
name: "delete_task",
|
|
20043
|
+
description: "Delete a task and its associated data. Cannot delete running tasks unless force=true.",
|
|
20044
|
+
inputSchema: {
|
|
20045
|
+
type: "object",
|
|
20046
|
+
properties: {
|
|
20047
|
+
task_id: {
|
|
20048
|
+
type: "string",
|
|
20049
|
+
description: "Task ID to delete"
|
|
20050
|
+
},
|
|
20051
|
+
force: {
|
|
20052
|
+
type: "boolean",
|
|
20053
|
+
default: false,
|
|
20054
|
+
description: "Force delete even if task is running"
|
|
20055
|
+
}
|
|
20056
|
+
},
|
|
20057
|
+
required: ["task_id"]
|
|
20058
|
+
}
|
|
20059
|
+
};
|
|
20060
|
+
|
|
18136
20061
|
// src/providers/mcp/pool-manager.ts
|
|
18137
20062
|
init_esm_shims();
|
|
18138
20063
|
init_logger();
|
|
@@ -18552,7 +20477,7 @@ var ConnectionTimeoutError = class extends McpClientError {
|
|
|
18552
20477
|
};
|
|
18553
20478
|
|
|
18554
20479
|
// src/providers/mcp/pool-manager.ts
|
|
18555
|
-
var
|
|
20480
|
+
var DEFAULT_CONFIG6 = {
|
|
18556
20481
|
maxConnectionsPerProvider: 2,
|
|
18557
20482
|
idleTimeoutMs: 3e5,
|
|
18558
20483
|
// 5 minutes
|
|
@@ -18573,7 +20498,7 @@ var McpClientPool = class extends EventEmitter {
|
|
|
18573
20498
|
drainHandler;
|
|
18574
20499
|
constructor(config = {}) {
|
|
18575
20500
|
super();
|
|
18576
|
-
this.config = { ...
|
|
20501
|
+
this.config = { ...DEFAULT_CONFIG6, ...config };
|
|
18577
20502
|
this.startHealthChecks();
|
|
18578
20503
|
this.startIdleCleanup();
|
|
18579
20504
|
this.drainHandler = () => this.drain();
|
|
@@ -20495,7 +22420,13 @@ var McpServer = class _McpServer {
|
|
|
20495
22420
|
},
|
|
20496
22421
|
required: ["agent", "task"]
|
|
20497
22422
|
}
|
|
20498
|
-
}
|
|
22423
|
+
},
|
|
22424
|
+
// v11.3.5: Task Engine tools
|
|
22425
|
+
createTaskSchema,
|
|
22426
|
+
runTaskSchema,
|
|
22427
|
+
getTaskResultSchema,
|
|
22428
|
+
listTasksSchema,
|
|
22429
|
+
deleteTaskSchema
|
|
20499
22430
|
];
|
|
20500
22431
|
}
|
|
20501
22432
|
/**
|
|
@@ -20670,6 +22601,15 @@ var McpServer = class _McpServer {
|
|
|
20670
22601
|
profileLoader: this.profileLoader,
|
|
20671
22602
|
memoryManager: this.memoryManager
|
|
20672
22603
|
}));
|
|
22604
|
+
register("create_task", createCreateTaskHandler({
|
|
22605
|
+
getSession: () => this.session
|
|
22606
|
+
}));
|
|
22607
|
+
register("run_task", createRunTaskHandler({
|
|
22608
|
+
getSession: () => this.session
|
|
22609
|
+
}));
|
|
22610
|
+
register("get_task_result", createGetTaskResultHandler());
|
|
22611
|
+
register("list_tasks", createListTasksHandler());
|
|
22612
|
+
register("delete_task", createDeleteTaskHandler());
|
|
20673
22613
|
logger.info("[MCP Server] Registered tools", {
|
|
20674
22614
|
count: this.tools.size,
|
|
20675
22615
|
tools: Array.from(this.tools.keys())
|