dist / tools / planTools.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.subagent_task = exports.audit_plan = exports.plan_manager = void 0;
const sdk_1 = require("@lmstudio/sdk");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const child_process_1 = require("child_process");
const util_1 = require("util");
const zod_1 = require("zod");
const utils_js_1 = require("../utils.js");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
function parsePlanMarkdown(content) {
const lines = content.split("\n");
let title = "Plan";
const tasks = [];
let index = 1;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith("# ")) {
title = trimmed.substring(2).trim();
}
else if (/^[-*]\s+\[([ xX])\]\s+(.*)$/.test(trimmed)) {
const match = trimmed.match(/^[-*]\s+\[([ xX])\]\s+(.*)$/);
if (match) {
const completed = match[1].toLowerCase() === "x";
const text = match[2].trim();
tasks.push({ index: index++, text, completed });
}
}
}
return { title, tasks };
}
function formatPlanMarkdown(title, description, tasks) {
const lines = [`# ${title || "Plan"}`];
if (description)
lines.push("", description.trim());
lines.push("", "## Tareas");
for (const t of tasks) {
lines.push(`- [${t.completed ? "x" : " "}] T${t.index}: ${t.text}`);
}
return lines.join("\n") + "\n";
}
// 1. plan_manager tool (ultra compacto)
exports.plan_manager = (0, sdk_1.tool)({
name: "plan_manager",
description: "Manage task plan in workspace (plan.md).",
parameters: {
action: zod_1.z.enum(["create", "read", "update_task", "add_task", "status"]).describe("Plan action."),
plan_path: zod_1.z.string().optional().describe("Plan file (default: plan.md)."),
title: zod_1.z.string().optional().describe("Plan title."),
description: zod_1.z.string().optional().describe("Plan description."),
tasks: zod_1.z.array(zod_1.z.string()).optional().describe("Tasks list for create."),
task_index: zod_1.z.number().optional().describe("1-based task number."),
completed: zod_1.z.boolean().optional().describe("Status for update_task."),
new_task: zod_1.z.string().optional().describe("Task description for add_task.")
},
implementation: async ({ action, plan_path, title, description, tasks, task_index, completed, new_task }) => {
try {
const targetPath = (0, utils_js_1.resolvePath)(plan_path || "plan.md");
if (action === "create") {
const taskList = (tasks || []).map((t, idx) => ({
index: idx + 1,
text: t,
completed: false
}));
const md = formatPlanMarkdown(title || "Plan", description, taskList);
await (0, promises_1.mkdir)((0, path_1.dirname)(targetPath), { recursive: true });
await (0, promises_1.writeFile)(targetPath, md, "utf-8");
return {
ok: true,
path: targetPath,
total: taskList.length,
msg: `Plan created (${taskList.length} tasks)`
};
}
if (action === "read" || action === "status") {
const content = await (0, promises_1.readFile)(targetPath, "utf-8");
const parsed = parsePlanMarkdown(content);
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
return {
ok: true,
title: parsed.title,
progress: `${done}/${total} (${total > 0 ? Math.round((done / total) * 100) : 0}%)`,
done: done === total,
tasks: parsed.tasks
};
}
if (action === "update_task") {
if (task_index === undefined)
return { ok: false, error: "task_index required" };
const content = await (0, promises_1.readFile)(targetPath, "utf-8");
const parsed = parsePlanMarkdown(content);
const task = parsed.tasks.find(t => t.index === task_index);
if (!task)
return { ok: false, error: `Task #${task_index} not found` };
task.completed = completed ?? true;
await (0, promises_1.writeFile)(targetPath, formatPlanMarkdown(parsed.title, undefined, parsed.tasks), "utf-8");
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
return {
ok: true,
progress: `${done}/${total} (${Math.round((done / total) * 100)}%)`,
task: `T${task_index} -> ${task.completed ? "DONE" : "PENDING"}`
};
}
if (action === "add_task") {
if (!new_task)
return { ok: false, error: "new_task required" };
let content = "";
try {
content = await (0, promises_1.readFile)(targetPath, "utf-8");
}
catch {
content = "# Plan\n\n## Tareas\n";
}
const parsed = parsePlanMarkdown(content);
const nextIdx = parsed.tasks.length + 1;
parsed.tasks.push({ index: nextIdx, text: new_task, completed: false });
await (0, promises_1.writeFile)(targetPath, formatPlanMarkdown(parsed.title, undefined, parsed.tasks), "utf-8");
return { ok: true, total: parsed.tasks.length, msg: `Added T${nextIdx}` };
}
return { ok: false, error: "Invalid action" };
}
catch (err) {
return { ok: false, error: err.message };
}
}
});
// 2. audit_plan tool (ultra compacto)
exports.audit_plan = (0, sdk_1.tool)({
name: "audit_plan",
description: "Audit plan completion: verifies required files exist and runs optional test command.",
parameters: {
plan_path: zod_1.z.string().optional().describe("Plan file (default: plan.md)."),
files_to_check: zod_1.z.array(zod_1.z.string()).optional().describe("Required file paths to verify."),
test_command: zod_1.z.string().optional().describe("Optional test command (e.g. 'npm test').")
},
implementation: async ({ plan_path, files_to_check, test_command }) => {
try {
const missing = [];
const present = [];
if (files_to_check && files_to_check.length > 0) {
for (const file of files_to_check) {
const fullPath = (0, utils_js_1.resolvePath)(file);
try {
const fileStat = await (0, promises_1.stat)(fullPath);
present.push(`${file} (${fileStat.size}B)`);
}
catch {
missing.push(file);
}
}
}
const targetPlan = (0, utils_js_1.resolvePath)(plan_path || "plan.md");
let planProgress = "No plan.md";
let planAllDone = true;
try {
const planContent = await (0, promises_1.readFile)(targetPlan, "utf-8");
const parsed = parsePlanMarkdown(planContent);
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
planProgress = `${done}/${total} tasks`;
planAllDone = total > 0 && done === total;
}
catch { }
let testOutput = "";
let testExitCode = 0;
if (test_command && test_command.trim()) {
const cwd = (0, utils_js_1.getWorkspaceDir)();
try {
const { stdout } = await execAsync(test_command, { cwd, timeout: 30000 });
testOutput = (0, utils_js_1.truncateOutput)(stdout.trim(), 400);
}
catch (testErr) {
testExitCode = testErr.code ?? 1;
testOutput = (0, utils_js_1.truncateOutput)(testErr.stderr ? String(testErr.stderr).trim() : testErr.message, 400);
}
}
const passed = missing.length === 0 && testExitCode === 0 && planAllDone;
return {
status: passed ? "PASSED" : "FAILED",
plan: planProgress,
missing_files: missing.length > 0 ? missing : undefined,
verified_files: present,
test_exit_code: test_command ? testExitCode : undefined,
test_summary: testOutput || undefined
};
}
catch (err) {
return { status: "FAILED", error: err.message };
}
}
});
// Endpoints de aceleración de hardware
const AMD_NODE_URL = process.env.AMD_NODE_URL || "http://127.0.0.1:1235/v1";
const INTEL_HOST_URL = process.env.INTEL_HOST_URL || "http://127.0.0.1:1234/v1";
async function fetchFirstModel(baseUrl) {
try {
const res = await fetch(`${baseUrl}/models`, { signal: AbortSignal.timeout(3000) });
if (res.ok) {
const data = (await res.json());
const list = data?.data || [];
if (list.length > 0 && list[0]?.id) {
return list[0].id;
}
}
}
catch { }
return null;
}
async function executeViaHttp(baseUrl, modelName, systemPrompt, userPrompt, maxTokens) {
const res = await fetch(`${baseUrl}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: modelName,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: maxTokens,
temperature: 0.3,
stream: false
}),
signal: AbortSignal.timeout(90000)
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
const data = (await res.json());
let content = data?.choices?.[0]?.message?.content || "";
if (content.includes("__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_")) {
const parts = content.split(/__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_[a-f0-9]+__/);
content = parts[parts.length - 1].trim();
}
return content.trim();
}
// 3. subagent_task tool con balanceo de carga multi-hardware
exports.subagent_task = (0, sdk_1.tool)({
name: "subagent_task",
description: "Delegate a focused task/review/refactoring to a subagent running on secondary hardware (AMD RX 480 eGPU or Intel Arc) to offload the main host.",
parameters: {
task_description: zod_1.z.string().describe("Concise subtask description for the subagent."),
context: zod_1.z.string().optional().describe("Brief context (keep concise for speed)."),
system_role: zod_1.z.string().optional().describe("Role e.g. 'Auditor', 'Coder', 'Reviewer', 'Optimizer'."),
target_hardware: zod_1.z.enum(["auto", "amd", "intel", "openvino"]).optional().default("auto").describe("Hardware to offload subtask to: 'amd' (dedicated RX 480 eGPU node :1235), 'intel' (host Arc :1234), or 'auto' (automatically routes to AMD eGPU to free main host)."),
max_tokens: zod_1.z.number().optional().default(600).describe("Max tokens to generate (default: 600).")
},
implementation: async ({ task_description, context, system_role, target_hardware = "auto", max_tokens = 600 }) => {
const systemPrompt = `You are a concise ${system_role || "assistant"}. Return strictly brief, minimal token results without conversational fluff.`;
let prompt = `TASK: ${task_description}`;
if (context)
prompt += `\nCONTEXT: ${context}`;
// 1. Ruta hacia AMD eGPU (:1235) si target es 'amd' o 'auto'
if (target_hardware === "amd" || target_hardware === "auto") {
try {
const amdModel = await fetchFirstModel(AMD_NODE_URL);
if (amdModel) {
const result = await executeViaHttp(AMD_NODE_URL, amdModel, systemPrompt, prompt, max_tokens);
return {
ok: true,
hardware: "AMD RX 480 (eGPU :1235)",
model: amdModel,
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(result, 1200)
};
}
}
catch (amdErr) {
if (target_hardware === "amd") {
return { ok: false, error: `AMD node error: ${amdErr.message}` };
}
// En auto, si falla AMD continuamos hacia fallback
}
}
// 2. Ruta hacia Host / Intel / OpenVINO (:1234) o fallback
try {
const intelModel = await fetchFirstModel(INTEL_HOST_URL);
if (intelModel) {
const result = await executeViaHttp(INTEL_HOST_URL, intelModel, systemPrompt, prompt, max_tokens);
return {
ok: true,
hardware: target_hardware === "openvino" ? "OpenVINO (Host :1234)" : "Intel Arc (Host :1234)",
model: intelModel,
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(result, 1200)
};
}
}
catch { }
// 3. Fallback directo al SDK local de LM Studio
try {
const client = new sdk_1.LMStudioClient();
const model = await client.llm.model();
const response = await model.respond([
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt }
], {
maxTokens: max_tokens
});
let outputText = response.content || "";
if (outputText.includes("__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_")) {
const parts = outputText.split(/__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_[a-f0-9]+__/);
outputText = parts[parts.length - 1].trim();
}
return {
ok: true,
hardware: "Local LM Studio Client",
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(outputText, 1200)
};
}
catch (err) {
return { ok: false, error: err.message };
}
}
});
dist / tools / planTools.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.subagent_task = exports.audit_plan = exports.plan_manager = void 0;
const sdk_1 = require("@lmstudio/sdk");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const child_process_1 = require("child_process");
const util_1 = require("util");
const zod_1 = require("zod");
const utils_js_1 = require("../utils.js");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
function parsePlanMarkdown(content) {
const lines = content.split("\n");
let title = "Plan";
const tasks = [];
let index = 1;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith("# ")) {
title = trimmed.substring(2).trim();
}
else if (/^[-*]\s+\[([ xX])\]\s+(.*)$/.test(trimmed)) {
const match = trimmed.match(/^[-*]\s+\[([ xX])\]\s+(.*)$/);
if (match) {
const completed = match[1].toLowerCase() === "x";
const text = match[2].trim();
tasks.push({ index: index++, text, completed });
}
}
}
return { title, tasks };
}
function formatPlanMarkdown(title, description, tasks) {
const lines = [`# ${title || "Plan"}`];
if (description)
lines.push("", description.trim());
lines.push("", "## Tareas");
for (const t of tasks) {
lines.push(`- [${t.completed ? "x" : " "}] T${t.index}: ${t.text}`);
}
return lines.join("\n") + "\n";
}
// 1. plan_manager tool (ultra compacto)
exports.plan_manager = (0, sdk_1.tool)({
name: "plan_manager",
description: "Manage task plan in workspace (plan.md).",
parameters: {
action: zod_1.z.enum(["create", "read", "update_task", "add_task", "status"]).describe("Plan action."),
plan_path: zod_1.z.string().optional().describe("Plan file (default: plan.md)."),
title: zod_1.z.string().optional().describe("Plan title."),
description: zod_1.z.string().optional().describe("Plan description."),
tasks: zod_1.z.array(zod_1.z.string()).optional().describe("Tasks list for create."),
task_index: zod_1.z.number().optional().describe("1-based task number."),
completed: zod_1.z.boolean().optional().describe("Status for update_task."),
new_task: zod_1.z.string().optional().describe("Task description for add_task.")
},
implementation: async ({ action, plan_path, title, description, tasks, task_index, completed, new_task }) => {
try {
const targetPath = (0, utils_js_1.resolvePath)(plan_path || "plan.md");
if (action === "create") {
const taskList = (tasks || []).map((t, idx) => ({
index: idx + 1,
text: t,
completed: false
}));
const md = formatPlanMarkdown(title || "Plan", description, taskList);
await (0, promises_1.mkdir)((0, path_1.dirname)(targetPath), { recursive: true });
await (0, promises_1.writeFile)(targetPath, md, "utf-8");
return {
ok: true,
path: targetPath,
total: taskList.length,
msg: `Plan created (${taskList.length} tasks)`
};
}
if (action === "read" || action === "status") {
const content = await (0, promises_1.readFile)(targetPath, "utf-8");
const parsed = parsePlanMarkdown(content);
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
return {
ok: true,
title: parsed.title,
progress: `${done}/${total} (${total > 0 ? Math.round((done / total) * 100) : 0}%)`,
done: done === total,
tasks: parsed.tasks
};
}
if (action === "update_task") {
if (task_index === undefined)
return { ok: false, error: "task_index required" };
const content = await (0, promises_1.readFile)(targetPath, "utf-8");
const parsed = parsePlanMarkdown(content);
const task = parsed.tasks.find(t => t.index === task_index);
if (!task)
return { ok: false, error: `Task #${task_index} not found` };
task.completed = completed ?? true;
await (0, promises_1.writeFile)(targetPath, formatPlanMarkdown(parsed.title, undefined, parsed.tasks), "utf-8");
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
return {
ok: true,
progress: `${done}/${total} (${Math.round((done / total) * 100)}%)`,
task: `T${task_index} -> ${task.completed ? "DONE" : "PENDING"}`
};
}
if (action === "add_task") {
if (!new_task)
return { ok: false, error: "new_task required" };
let content = "";
try {
content = await (0, promises_1.readFile)(targetPath, "utf-8");
}
catch {
content = "# Plan\n\n## Tareas\n";
}
const parsed = parsePlanMarkdown(content);
const nextIdx = parsed.tasks.length + 1;
parsed.tasks.push({ index: nextIdx, text: new_task, completed: false });
await (0, promises_1.writeFile)(targetPath, formatPlanMarkdown(parsed.title, undefined, parsed.tasks), "utf-8");
return { ok: true, total: parsed.tasks.length, msg: `Added T${nextIdx}` };
}
return { ok: false, error: "Invalid action" };
}
catch (err) {
return { ok: false, error: err.message };
}
}
});
// 2. audit_plan tool (ultra compacto)
exports.audit_plan = (0, sdk_1.tool)({
name: "audit_plan",
description: "Audit plan completion: verifies required files exist and runs optional test command.",
parameters: {
plan_path: zod_1.z.string().optional().describe("Plan file (default: plan.md)."),
files_to_check: zod_1.z.array(zod_1.z.string()).optional().describe("Required file paths to verify."),
test_command: zod_1.z.string().optional().describe("Optional test command (e.g. 'npm test').")
},
implementation: async ({ plan_path, files_to_check, test_command }) => {
try {
const missing = [];
const present = [];
if (files_to_check && files_to_check.length > 0) {
for (const file of files_to_check) {
const fullPath = (0, utils_js_1.resolvePath)(file);
try {
const fileStat = await (0, promises_1.stat)(fullPath);
present.push(`${file} (${fileStat.size}B)`);
}
catch {
missing.push(file);
}
}
}
const targetPlan = (0, utils_js_1.resolvePath)(plan_path || "plan.md");
let planProgress = "No plan.md";
let planAllDone = true;
try {
const planContent = await (0, promises_1.readFile)(targetPlan, "utf-8");
const parsed = parsePlanMarkdown(planContent);
const total = parsed.tasks.length;
const done = parsed.tasks.filter(t => t.completed).length;
planProgress = `${done}/${total} tasks`;
planAllDone = total > 0 && done === total;
}
catch { }
let testOutput = "";
let testExitCode = 0;
if (test_command && test_command.trim()) {
const cwd = (0, utils_js_1.getWorkspaceDir)();
try {
const { stdout } = await execAsync(test_command, { cwd, timeout: 30000 });
testOutput = (0, utils_js_1.truncateOutput)(stdout.trim(), 400);
}
catch (testErr) {
testExitCode = testErr.code ?? 1;
testOutput = (0, utils_js_1.truncateOutput)(testErr.stderr ? String(testErr.stderr).trim() : testErr.message, 400);
}
}
const passed = missing.length === 0 && testExitCode === 0 && planAllDone;
return {
status: passed ? "PASSED" : "FAILED",
plan: planProgress,
missing_files: missing.length > 0 ? missing : undefined,
verified_files: present,
test_exit_code: test_command ? testExitCode : undefined,
test_summary: testOutput || undefined
};
}
catch (err) {
return { status: "FAILED", error: err.message };
}
}
});
// Endpoints de aceleración de hardware
const AMD_NODE_URL = process.env.AMD_NODE_URL || "http://127.0.0.1:1235/v1";
const INTEL_HOST_URL = process.env.INTEL_HOST_URL || "http://127.0.0.1:1234/v1";
async function fetchFirstModel(baseUrl) {
try {
const res = await fetch(`${baseUrl}/models`, { signal: AbortSignal.timeout(3000) });
if (res.ok) {
const data = (await res.json());
const list = data?.data || [];
if (list.length > 0 && list[0]?.id) {
return list[0].id;
}
}
}
catch { }
return null;
}
async function executeViaHttp(baseUrl, modelName, systemPrompt, userPrompt, maxTokens) {
const res = await fetch(`${baseUrl}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: modelName,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: maxTokens,
temperature: 0.3,
stream: false
}),
signal: AbortSignal.timeout(90000)
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
const data = (await res.json());
let content = data?.choices?.[0]?.message?.content || "";
if (content.includes("__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_")) {
const parts = content.split(/__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_[a-f0-9]+__/);
content = parts[parts.length - 1].trim();
}
return content.trim();
}
// 3. subagent_task tool con balanceo de carga multi-hardware
exports.subagent_task = (0, sdk_1.tool)({
name: "subagent_task",
description: "Delegate a focused task/review/refactoring to a subagent running on secondary hardware (AMD RX 480 eGPU or Intel Arc) to offload the main host.",
parameters: {
task_description: zod_1.z.string().describe("Concise subtask description for the subagent."),
context: zod_1.z.string().optional().describe("Brief context (keep concise for speed)."),
system_role: zod_1.z.string().optional().describe("Role e.g. 'Auditor', 'Coder', 'Reviewer', 'Optimizer'."),
target_hardware: zod_1.z.enum(["auto", "amd", "intel", "openvino"]).optional().default("auto").describe("Hardware to offload subtask to: 'amd' (dedicated RX 480 eGPU node :1235), 'intel' (host Arc :1234), or 'auto' (automatically routes to AMD eGPU to free main host)."),
max_tokens: zod_1.z.number().optional().default(600).describe("Max tokens to generate (default: 600).")
},
implementation: async ({ task_description, context, system_role, target_hardware = "auto", max_tokens = 600 }) => {
const systemPrompt = `You are a concise ${system_role || "assistant"}. Return strictly brief, minimal token results without conversational fluff.`;
let prompt = `TASK: ${task_description}`;
if (context)
prompt += `\nCONTEXT: ${context}`;
// 1. Ruta hacia AMD eGPU (:1235) si target es 'amd' o 'auto'
if (target_hardware === "amd" || target_hardware === "auto") {
try {
const amdModel = await fetchFirstModel(AMD_NODE_URL);
if (amdModel) {
const result = await executeViaHttp(AMD_NODE_URL, amdModel, systemPrompt, prompt, max_tokens);
return {
ok: true,
hardware: "AMD RX 480 (eGPU :1235)",
model: amdModel,
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(result, 1200)
};
}
}
catch (amdErr) {
if (target_hardware === "amd") {
return { ok: false, error: `AMD node error: ${amdErr.message}` };
}
// En auto, si falla AMD continuamos hacia fallback
}
}
// 2. Ruta hacia Host / Intel / OpenVINO (:1234) o fallback
try {
const intelModel = await fetchFirstModel(INTEL_HOST_URL);
if (intelModel) {
const result = await executeViaHttp(INTEL_HOST_URL, intelModel, systemPrompt, prompt, max_tokens);
return {
ok: true,
hardware: target_hardware === "openvino" ? "OpenVINO (Host :1234)" : "Intel Arc (Host :1234)",
model: intelModel,
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(result, 1200)
};
}
}
catch { }
// 3. Fallback directo al SDK local de LM Studio
try {
const client = new sdk_1.LMStudioClient();
const model = await client.llm.model();
const response = await model.respond([
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt }
], {
maxTokens: max_tokens
});
let outputText = response.content || "";
if (outputText.includes("__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_")) {
const parts = outputText.split(/__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_[a-f0-9]+__/);
outputText = parts[parts.length - 1].trim();
}
return {
ok: true,
hardware: "Local LM Studio Client",
role: system_role || "Subagent",
result: (0, utils_js_1.truncateOutput)(outputText, 1200)
};
}
catch (err) {
return { ok: false, error: err.message };
}
}
});