daemora 1.0.4 → 1.0.6

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.
Files changed (123) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +69 -19
  3. package/SOUL.md +29 -26
  4. package/config/mcp.json +126 -66
  5. package/daemora-ui/README.md +11 -0
  6. package/package.json +12 -2
  7. package/skills/api-development.md +35 -0
  8. package/skills/artifacts-builder/SKILL.md +74 -0
  9. package/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  10. package/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
  11. package/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  12. package/skills/brand-guidelines.md +73 -0
  13. package/skills/browser.md +77 -0
  14. package/skills/changelog-generator.md +104 -0
  15. package/skills/coding.md +26 -10
  16. package/skills/content-research-writer.md +538 -0
  17. package/skills/data-analysis.md +27 -0
  18. package/skills/debugging.md +33 -0
  19. package/skills/devops.md +37 -0
  20. package/skills/document-docx.md +197 -0
  21. package/skills/document-pdf.md +294 -0
  22. package/skills/document-pptx.md +484 -0
  23. package/skills/document-xlsx.md +289 -0
  24. package/skills/domain-name-brainstormer.md +212 -0
  25. package/skills/file-organizer.md +433 -0
  26. package/skills/frontend-design.md +42 -0
  27. package/skills/image-enhancer.md +99 -0
  28. package/skills/invoice-organizer.md +446 -0
  29. package/skills/lead-research-assistant.md +199 -0
  30. package/skills/mcp-builder/SKILL.md +328 -0
  31. package/skills/mcp-builder/reference/evaluation.md +602 -0
  32. package/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  33. package/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  34. package/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  35. package/skills/mcp-builder/scripts/connections.py +151 -0
  36. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  37. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  38. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  39. package/skills/meeting-insights-analyzer.md +327 -0
  40. package/skills/orchestration.md +93 -0
  41. package/skills/raffle-winner-picker.md +159 -0
  42. package/skills/slack-gif-creator/SKILL.md +646 -0
  43. package/skills/slack-gif-creator/core/color_palettes.py +302 -0
  44. package/skills/slack-gif-creator/core/easing.py +230 -0
  45. package/skills/slack-gif-creator/core/frame_composer.py +469 -0
  46. package/skills/slack-gif-creator/core/gif_builder.py +246 -0
  47. package/skills/slack-gif-creator/core/typography.py +357 -0
  48. package/skills/slack-gif-creator/core/validators.py +264 -0
  49. package/skills/slack-gif-creator/core/visual_effects.py +494 -0
  50. package/skills/slack-gif-creator/requirements.txt +4 -0
  51. package/skills/slack-gif-creator/templates/bounce.py +106 -0
  52. package/skills/slack-gif-creator/templates/explode.py +331 -0
  53. package/skills/slack-gif-creator/templates/fade.py +329 -0
  54. package/skills/slack-gif-creator/templates/flip.py +291 -0
  55. package/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
  56. package/skills/slack-gif-creator/templates/morph.py +329 -0
  57. package/skills/slack-gif-creator/templates/move.py +293 -0
  58. package/skills/slack-gif-creator/templates/pulse.py +268 -0
  59. package/skills/slack-gif-creator/templates/shake.py +127 -0
  60. package/skills/slack-gif-creator/templates/slide.py +291 -0
  61. package/skills/slack-gif-creator/templates/spin.py +269 -0
  62. package/skills/slack-gif-creator/templates/wiggle.py +300 -0
  63. package/skills/slack-gif-creator/templates/zoom.py +312 -0
  64. package/skills/system-admin.md +44 -0
  65. package/skills/tailored-resume-generator.md +345 -0
  66. package/skills/theme-factory/SKILL.md +59 -0
  67. package/skills/theme-factory/theme-showcase.pdf +0 -0
  68. package/skills/theme-factory/themes/arctic-frost.md +19 -0
  69. package/skills/theme-factory/themes/botanical-garden.md +19 -0
  70. package/skills/theme-factory/themes/desert-rose.md +19 -0
  71. package/skills/theme-factory/themes/forest-canopy.md +19 -0
  72. package/skills/theme-factory/themes/golden-hour.md +19 -0
  73. package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  74. package/skills/theme-factory/themes/modern-minimalist.md +19 -0
  75. package/skills/theme-factory/themes/ocean-depths.md +19 -0
  76. package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  77. package/skills/theme-factory/themes/tech-innovation.md +19 -0
  78. package/skills/video-downloader.md +99 -0
  79. package/skills/web-development.md +32 -0
  80. package/skills/webapp-testing/SKILL.md +96 -0
  81. package/skills/webapp-testing/examples/console_logging.py +35 -0
  82. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  83. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  84. package/skills/webapp-testing/scripts/with_server.py +106 -0
  85. package/src/agents/SubAgentManager.js +134 -16
  86. package/src/agents/systemPrompt.js +427 -0
  87. package/src/api/openai-compat.js +212 -0
  88. package/src/channels/TelegramChannel.js +5 -2
  89. package/src/channels/index.js +7 -10
  90. package/src/cli.js +281 -55
  91. package/src/config/agentProfiles.js +1 -0
  92. package/src/config/default.js +15 -1
  93. package/src/config/models.js +314 -78
  94. package/src/config/permissions.js +12 -0
  95. package/src/core/AgentLoop.js +70 -50
  96. package/src/core/Compaction.js +111 -11
  97. package/src/core/MessageQueue.js +90 -0
  98. package/src/core/Task.js +13 -0
  99. package/src/core/TaskQueue.js +1 -1
  100. package/src/core/TaskRunner.js +81 -6
  101. package/src/index.js +725 -59
  102. package/src/mcp/MCPAgentRunner.js +48 -11
  103. package/src/mcp/MCPManager.js +40 -2
  104. package/src/models/ModelRouter.js +74 -4
  105. package/src/safety/DockerSandbox.js +212 -0
  106. package/src/safety/ExecApproval.js +118 -0
  107. package/src/scheduler/Heartbeat.js +56 -21
  108. package/src/services/cleanup.js +106 -0
  109. package/src/services/sessions.js +39 -1
  110. package/src/setup/wizard.js +125 -75
  111. package/src/skills/SkillLoader.js +132 -17
  112. package/src/storage/TaskStore.js +19 -1
  113. package/src/tools/browserAutomation.js +615 -104
  114. package/src/tools/executeCommand.js +19 -1
  115. package/src/tools/index.js +7 -1
  116. package/src/tools/manageAgents.js +55 -4
  117. package/src/tools/replyWithFile.js +62 -0
  118. package/src/tools/screenCapture.js +12 -1
  119. package/src/tools/taskManager.js +164 -0
  120. package/src/tools/useMCP.js +3 -1
  121. package/src/utils/Embeddings.js +236 -12
  122. package/src/webhooks/WebhookHandler.js +107 -0
  123. package/src/systemPrompt.js +0 -528
@@ -13,12 +13,15 @@ import { resolve } from "node:path";
13
13
  import { config } from "../config/default.js";
14
14
  import filesystemGuard from "../safety/FilesystemGuard.js";
15
15
  import { checkCommand } from "../safety/CommandGuard.js";
16
+ import execApproval from "../safety/ExecApproval.js";
17
+ import dockerSandbox from "../safety/DockerSandbox.js";
18
+ import tenantContext from "../tenants/TenantContext.js";
16
19
 
17
20
  const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes default
18
21
  const MAX_TIMEOUT_MS = 600_000; // 10 minutes hard max
19
22
  const MAX_BUFFER = 10 * 1024 * 1024; // 10MB
20
23
 
21
- export function executeCommand(cmd, optionsJson) {
24
+ export async function executeCommand(cmd, optionsJson) {
22
25
  const opts = optionsJson ? JSON.parse(optionsJson) : {};
23
26
  const {
24
27
  cwd: cwdRaw = null,
@@ -42,6 +45,14 @@ export function executeCommand(cmd, optionsJson) {
42
45
  if (!cmdCheck.allowed) {
43
46
  return `Command blocked by security policy: ${cmdCheck.reason}`;
44
47
  }
48
+
49
+ // ── Exec approval gate — pause for user approval if needed ──
50
+ if (execApproval.needsApproval(cmd)) {
51
+ const decision = await execApproval.requestApproval(cmd, opts.taskId || null);
52
+ if (decision === "deny") {
53
+ return `Command denied by approval gate: "${cmd.slice(0, 80)}". User chose to deny execution.`;
54
+ }
55
+ }
45
56
  // ──────────────────────────────────────────────────────────────────────────
46
57
 
47
58
  // ── Filesystem scope enforcement ───────────────────────────────────────────
@@ -80,6 +91,13 @@ export function executeCommand(cmd, optionsJson) {
80
91
 
81
92
  console.log(` [executeCommand] Running: ${cmd}${cwdRaw ? ` (cwd: ${cwdRaw})` : ""}${background ? " [background]" : ""}`);
82
93
 
94
+ // ── Docker sandbox mode — route through container ──
95
+ if (config.sandbox?.mode === "docker" && dockerSandbox.isAvailable() && !background) {
96
+ const store = tenantContext.getStore();
97
+ const scope = config.sandbox.docker?.scope === "shared" ? "shared" : (store?.sessionId || "shared");
98
+ return dockerSandbox.exec(scope, cmd, { timeout, cwd });
99
+ }
100
+
83
101
  // Background mode - spawn detached, return PID immediately
84
102
  if (background) {
85
103
  try {
@@ -26,6 +26,7 @@ import { delegateToAgent, delegateToAgentDescription } from "../a2a/A2AClient.js
26
26
  // ─── Media tools ───────────────────────────────────────────────────────────────
27
27
  import { transcribeAudio, transcribeAudioDescription } from "./transcribeAudio.js";
28
28
  import { sendFile, sendFileDescription } from "./sendFile.js";
29
+ import { replyWithFile, replyWithFileDescription } from "./replyWithFile.js";
29
30
  import { textToSpeech, textToSpeechDescription } from "./textToSpeech.js";
30
31
 
31
32
  // ─── Search & code tools ───────────────────────────────────────────────────────
@@ -38,6 +39,7 @@ import { manageAgents, manageAgentsDescription } from "./manageAgents.js";
38
39
  import { cron, cronDescription } from "./cronTool.js";
39
40
  import { messageChannel, messageChannelDescription } from "./messageChannel.js";
40
41
  import { projectTracker, projectTrackerDescription } from "./projectTracker.js";
42
+ import { taskManager, taskManagerDescription } from "./taskManager.js";
41
43
  import { manageMCP, manageMCPDescription } from "./manageMCP.js";
42
44
  import { useMCP, useMCPDescription } from "./useMCP.js";
43
45
  import { makeVoiceCall, makeVoiceCallDescription } from "./makeVoiceCall.js";
@@ -64,7 +66,7 @@ function spawnAgent(taskDescription, optionsJson) {
64
66
  }
65
67
 
66
68
  const spawnAgentDescription =
67
- 'spawnAgent(taskDescription: string, optionsJson?: string) - Spawn a sub-agent to handle a task independently. optionsJson: {"model":"openai:gpt-4.1-mini","tools":["readFile","searchContent"],"maxTurns":10,"parentContext":"shared spec here"}';
69
+ 'spawnAgent(taskDescription: string, optionsJson?: string) - Spawn a sub-agent to handle a task independently. optionsJson: {"model":"openai:gpt-4.1-mini","tools":["readFile","searchContent"],"skills":["skills/coding.md","skills/brand-guidelines.md"],"parentContext":"shared spec here"}. Pass skills array with paths from the skills list to inject skill content directly into the sub-agent.';
68
70
 
69
71
  // ─── Wrap parallelAgents for the tool interface ────────────────────────────────
70
72
  function parallelAgents(tasksJson, sharedOptionsJson) {
@@ -105,6 +107,7 @@ export const toolFunctions = {
105
107
  sendEmail,
106
108
  messageChannel,
107
109
  sendFile,
110
+ replyWithFile,
108
111
  transcribeAudio,
109
112
  textToSpeech,
110
113
  // Documents
@@ -124,6 +127,7 @@ export const toolFunctions = {
124
127
  manageAgents,
125
128
  // Project tracking
126
129
  projectTracker,
130
+ taskManager,
127
131
  // Automation
128
132
  cron,
129
133
  // Vision / Screen
@@ -178,6 +182,7 @@ export const toolDescriptions = [
178
182
  sendEmailDescription,
179
183
  messageChannelDescription,
180
184
  sendFileDescription,
185
+ replyWithFileDescription,
181
186
  transcribeAudioDescription,
182
187
  textToSpeechDescription,
183
188
  // Documents
@@ -197,6 +202,7 @@ export const toolDescriptions = [
197
202
  manageAgentsDescription,
198
203
  // Project tracking
199
204
  projectTrackerDescription,
205
+ taskManagerDescription,
200
206
  // Automation
201
207
  cronDescription,
202
208
  // Vision / Screen
@@ -1,12 +1,13 @@
1
1
  /**
2
- * manageAgents(action, paramsJson?) - List, kill, or steer running sub-agents.
3
- * Inspired by OpenClaw's subagents tool.
2
+ * manageAgents(action, paramsJson?) - List, kill, or steer running sub-agents + manage persistent sessions.
4
3
  */
5
4
  import {
6
5
  listActiveAgents,
7
6
  killAgent,
8
7
  steerAgent,
9
8
  } from "../agents/SubAgentManager.js";
9
+ import { listSessions, getSession, clearSession } from "../services/sessions.js";
10
+ import tenantContext from "../tenants/TenantContext.js";
10
11
 
11
12
  export function manageAgents(action, paramsJson) {
12
13
  try {
@@ -35,8 +36,55 @@ export function manageAgents(action, paramsJson) {
35
36
  return result;
36
37
  }
37
38
 
39
+ // ── Persistent sub-agent session management ──────────────────────────
40
+ case "sessions": {
41
+ const store = tenantContext.getStore();
42
+ const mainSessionId = store?.sessionId;
43
+ if (!mainSessionId) return "No active session context.";
44
+
45
+ const subSessions = listSessions(mainSessionId);
46
+ if (subSessions.length === 0) return "No sub-agent sessions found.";
47
+
48
+ const lines = subSessions.map(id => {
49
+ const label = id.slice(mainSessionId.length + 2); // strip "telegram-123--"
50
+ const session = getSession(id);
51
+ const msgCount = session?.messages?.length || 0;
52
+ return `• ${label} (${msgCount} messages) — sessionId: "${id}"`;
53
+ });
54
+ return `Sub-agent sessions (${subSessions.length}):\n${lines.join("\n")}`;
55
+ }
56
+
57
+ case "session_get": {
58
+ if (!params.sessionId) return 'Error: sessionId is required for "session_get" action';
59
+ const session = getSession(params.sessionId);
60
+ if (!session) return `Session "${params.sessionId}" not found.`;
61
+ const count = params.count || 5;
62
+ const last = session.messages.slice(-count);
63
+ if (last.length === 0) return "Session exists but has no messages.";
64
+ return `Last ${last.length} messages from "${params.sessionId}":\n\n` +
65
+ last.map(m => `[${m.role}]: ${(m.content || "").slice(0, 300)}`).join("\n\n");
66
+ }
67
+
68
+ case "session_clear": {
69
+ if (!params.sessionId) return 'Error: sessionId is required for "session_clear" action';
70
+ const cleared = clearSession(params.sessionId);
71
+ return cleared
72
+ ? `Session "${params.sessionId}" cleared.`
73
+ : `Session "${params.sessionId}" not found.`;
74
+ }
75
+
76
+ case "session_clear_all": {
77
+ const store = tenantContext.getStore();
78
+ const mainSessionId = store?.sessionId;
79
+ if (!mainSessionId) return "No active session context.";
80
+ const subSessions = listSessions(mainSessionId);
81
+ if (subSessions.length === 0) return "No sub-agent sessions to clear.";
82
+ subSessions.forEach(id => clearSession(id));
83
+ return `Cleared ${subSessions.length} sub-agent session(s).`;
84
+ }
85
+
38
86
  default:
39
- return `Unknown action: "${action}". Available: list, kill, steer`;
87
+ return `Unknown action: "${action}". Available: list, kill, steer, sessions, session_get, session_clear, session_clear_all`;
40
88
  }
41
89
  } catch (error) {
42
90
  return `Error managing agents: ${error.message}`;
@@ -44,4 +92,7 @@ export function manageAgents(action, paramsJson) {
44
92
  }
45
93
 
46
94
  export const manageAgentsDescription =
47
- 'manageAgents(action: string, paramsJson?: string) - Manage running sub-agents. Actions: "list" (show all), "kill" ({"agentId":"id"}), "steer" ({"agentId":"id","message":"new instruction"}).';
95
+ 'manageAgents(action: string, paramsJson?: string) - Manage sub-agents and their persistent sessions. ' +
96
+ 'Actions: "list" (running agents), "kill" ({"agentId":"id"}), "steer" ({"agentId":"id","message":"..."}), ' +
97
+ '"sessions" (list persistent sub-agent sessions), "session_get" ({"sessionId":"id","count":5} - last N messages), ' +
98
+ '"session_clear" ({"sessionId":"id"} - reset a specialist), "session_clear_all" (clear all sub-agent sessions).';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * replyWithFile(filePath, caption?) - Send a file back to the user who sent the current message.
3
+ *
4
+ * Reads channel + chatId from TenantContext automatically.
5
+ * The agent never needs to know which channel or chatId — just the file path.
6
+ *
7
+ * Works for images, videos, documents, audio — any file type.
8
+ * The channel adapter auto-detects the type and sends appropriately
9
+ * (e.g. Telegram sends photos as photos, videos as videos).
10
+ */
11
+ import tenantContext from "../tenants/TenantContext.js";
12
+ import channelRegistry from "../channels/index.js";
13
+ import { existsSync, statSync } from "node:fs";
14
+
15
+ const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
16
+
17
+ export async function replyWithFile(filePath, caption) {
18
+ try {
19
+ if (!filePath) return "Error: filePath is required.";
20
+
21
+ if (!existsSync(filePath)) {
22
+ return `Error: File not found: ${filePath}`;
23
+ }
24
+
25
+ const size = statSync(filePath).size;
26
+ if (size > MAX_FILE_SIZE) {
27
+ return `Error: File too large (${(size / 1024 / 1024).toFixed(1)} MB). Maximum is 50 MB.`;
28
+ }
29
+
30
+ const store = tenantContext.getStore();
31
+ const channelMeta = store?.channelMeta;
32
+
33
+ if (!channelMeta?.channel || !channelMeta?.chatId) {
34
+ return "Error: No active channel context. Cannot determine where to send the file. Use sendFile(channel, target, filePath) instead.";
35
+ }
36
+
37
+ const ch = channelRegistry.get(channelMeta.channel);
38
+ if (!ch) {
39
+ return `Error: Channel "${channelMeta.channel}" not available.`;
40
+ }
41
+ if (!ch.running) {
42
+ return `Error: Channel "${channelMeta.channel}" is not running.`;
43
+ }
44
+ if (typeof ch.sendFile !== "function") {
45
+ return `Error: Channel "${channelMeta.channel}" does not support file sending.`;
46
+ }
47
+
48
+ await ch.sendFile(channelMeta, filePath, caption || "");
49
+
50
+ // Mark that we already replied directly — channel should skip the duplicate text message
51
+ if (store) store.directReplySent = true;
52
+
53
+ return `File sent to user: ${filePath}`;
54
+ } catch (error) {
55
+ return `Error sending file: ${error.message}`;
56
+ }
57
+ }
58
+
59
+ export const replyWithFileDescription =
60
+ 'replyWithFile(filePath, caption?) - Send a file (image, video, document, audio) back to the user. ' +
61
+ 'Automatically uses the current channel — no need to specify channel or chatId. ' +
62
+ 'Use after screenCapture, generateImage, createDocument, or any tool that produces a file the user should receive.';
@@ -12,7 +12,7 @@
12
12
  * or sendFile to deliver the result to the user.
13
13
  */
14
14
  import { execSync } from "node:child_process";
15
- import { existsSync, mkdirSync } from "node:fs";
15
+ import { existsSync, mkdirSync, statSync } from "node:fs";
16
16
  import { platform } from "node:os";
17
17
  import { join } from "node:path";
18
18
 
@@ -61,8 +61,19 @@ export function screenCapture(optionsJson) {
61
61
  }
62
62
 
63
63
  if (!existsSync(outputPath)) {
64
+ if (os === "darwin") {
65
+ return "Error: Screenshot failed. The terminal app likely needs Screen Recording permission. Go to: System Settings → Privacy & Security → Screen Recording → enable your terminal app, then restart it.";
66
+ }
64
67
  return "Error: Screenshot command ran but no file was created.";
65
68
  }
69
+
70
+ if (os === "darwin") {
71
+ const fileSize = statSync(outputPath).size;
72
+ if (fileSize < 500) {
73
+ return `Error: Screenshot captured but appears empty (${fileSize} bytes). The terminal app likely needs Screen Recording permission. Go to: System Settings → Privacy & Security → Screen Recording → enable your terminal app, then restart it.`;
74
+ }
75
+ }
76
+
66
77
  return `Screenshot saved to: ${outputPath}`;
67
78
  }
68
79
 
@@ -0,0 +1,164 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { createTask, startTask, completeTask, failTask } from "../core/Task.js";
3
+ import { saveTask, loadTask, listTasks, listChildTasks } from "../storage/TaskStore.js";
4
+ import tenantContext from "../tenants/TenantContext.js";
5
+
6
+ /**
7
+ * Task Manager - Agent-facing tool for creating and tracking tasks.
8
+ *
9
+ * Unlike projectTracker (which is a separate project/task system),
10
+ * this creates real Task records in TaskStore that appear in the UI
11
+ * and integrate with sub-agent tracking.
12
+ *
13
+ * Actions:
14
+ * createTask - create a new agent task (type: "task")
15
+ * updateTask - update status/result of a task
16
+ * listTasks - list agent-created tasks
17
+ * getTask - get full task details + children
18
+ */
19
+
20
+ export function taskManager(action, paramsJson) {
21
+ const params = paramsJson
22
+ ? (typeof paramsJson === "string" ? JSON.parse(paramsJson) : paramsJson)
23
+ : {};
24
+
25
+ // Get current context for parentTaskId linkage
26
+ const store = tenantContext.getStore();
27
+
28
+ switch (action) {
29
+
30
+ // ── Create task ─────────────────────────────────────────────────────────
31
+ case "createTask": {
32
+ const { title, description = "", status = "pending", parentTaskId = null } = params;
33
+ if (!title) return "Error: title is required";
34
+
35
+ // Auto-link to current executing task if no explicit parent
36
+ const effectiveParentId = parentTaskId || store?.currentTaskId || null;
37
+
38
+ const task = createTask({
39
+ input: description || title,
40
+ type: "task",
41
+ title,
42
+ description,
43
+ parentTaskId: effectiveParentId,
44
+ agentCreated: true,
45
+ agentId: store?.agentId || null,
46
+ channel: "agent",
47
+ sessionId: store?.sessionId || null,
48
+ });
49
+
50
+ // If created with a non-pending status, apply it
51
+ if (status === "in_progress") startTask(task);
52
+ else if (status === "completed") {
53
+ startTask(task);
54
+ completeTask(task, "Created as completed");
55
+ }
56
+
57
+ saveTask(task);
58
+
59
+ const parentStr = effectiveParentId ? ` (child of ${effectiveParentId.slice(0, 8)})` : "";
60
+ return `Task created: ${task.id} "${title}"${parentStr} — status: ${task.status}`;
61
+ }
62
+
63
+ // ── Update task ─────────────────────────────────────────────────────────
64
+ case "updateTask": {
65
+ const { taskId, status, result, agentId } = params;
66
+ if (!taskId) return "Error: taskId is required";
67
+
68
+ const task = loadTask(taskId);
69
+ if (!task) return `Error: Task "${taskId}" not found`;
70
+
71
+ if (agentId) task.agentId = agentId;
72
+
73
+ if (status) {
74
+ const oldStatus = task.status;
75
+ switch (status) {
76
+ case "in_progress":
77
+ if (task.status === "pending") startTask(task);
78
+ else task.status = "in_progress";
79
+ break;
80
+ case "completed":
81
+ completeTask(task, result || task.result || "");
82
+ break;
83
+ case "failed":
84
+ failTask(task, result || "Task failed");
85
+ break;
86
+ default:
87
+ return `Error: Invalid status "${status}". Use: pending, in_progress, completed, failed`;
88
+ }
89
+ saveTask(task);
90
+ return `Task ${taskId} "${task.title || task.input?.slice(0, 40)}": ${oldStatus} → ${status}`;
91
+ }
92
+
93
+ saveTask(task);
94
+ return `Task ${taskId} updated`;
95
+ }
96
+
97
+ // ── List tasks ──────────────────────────────────────────────────────────
98
+ case "listTasks": {
99
+ const { status = null, parentTaskId = null, limit = 20 } = params;
100
+
101
+ let tasks = listTasks({ limit, status, type: "task" });
102
+
103
+ if (parentTaskId) {
104
+ tasks = tasks.filter(t => t.parentTaskId === parentTaskId);
105
+ }
106
+
107
+ if (tasks.length === 0) return "No agent-created tasks found.";
108
+
109
+ return tasks.map(t => {
110
+ const icon = t.status === "completed" ? "✅" : t.status === "running" ? "🔄" : t.status === "failed" ? "❌" : "⬜";
111
+ const agent = t.agentId ? ` [agent:${t.agentId}]` : "";
112
+ const parent = t.parentTaskId ? ` ← ${t.parentTaskId}` : "";
113
+ return `${icon} ${t.id} ${t.title || t.input?.slice(0, 50)}${agent}${parent} — ${t.status}`;
114
+ }).join("\n");
115
+ }
116
+
117
+ // ── Get task details ────────────────────────────────────────────────────
118
+ case "getTask": {
119
+ const { taskId } = params;
120
+ if (!taskId) return "Error: taskId is required";
121
+
122
+ const task = loadTask(taskId);
123
+ if (!task) return `Error: Task "${taskId}" not found`;
124
+
125
+ const children = listChildTasks(taskId);
126
+
127
+ const lines = [
128
+ `Task: ${task.title || task.input?.slice(0, 60)} [${task.id.slice(0, 8)}]`,
129
+ `Type: ${task.type || "chat"} | Status: ${task.status}`,
130
+ task.description ? `Description: ${task.description}` : null,
131
+ task.agentId ? `Agent: ${task.agentId}` : null,
132
+ task.parentTaskId ? `Parent: ${task.parentTaskId.slice(0, 8)}` : null,
133
+ task.cost?.estimatedCost ? `Cost: $${task.cost.estimatedCost.toFixed(4)}` : null,
134
+ task.toolCalls?.length ? `Tool calls: ${task.toolCalls.length}` : null,
135
+ task.subAgents?.length ? `Sub-agents: ${task.subAgents.length}` : null,
136
+ ].filter(Boolean);
137
+
138
+ if (children.length > 0) {
139
+ lines.push("", `Children (${children.length}):`);
140
+ for (const child of children) {
141
+ const icon = child.status === "completed" ? "✅" : child.status === "running" ? "🔄" : child.status === "failed" ? "❌" : "⬜";
142
+ const agent = child.agentId ? ` [${child.agentId.slice(0, 8)}]` : "";
143
+ lines.push(` ${icon} [${child.id.slice(0, 8)}] ${child.title || child.input?.slice(0, 40)}${agent} — ${child.status}`);
144
+ }
145
+ }
146
+
147
+ return lines.join("\n");
148
+ }
149
+
150
+ default:
151
+ return `Unknown action: "${action}". Valid: createTask, updateTask, listTasks, getTask`;
152
+ }
153
+ }
154
+
155
+ export const taskManagerDescription =
156
+ `taskManager(action: string, paramsJson?: string) - Create, update, and monitor tasks. Tasks appear in the UI and link to sub-agents.
157
+ Actions:
158
+ createTask - {"title":"...","description":"...","status":"pending|in_progress"} → returns full task ID
159
+ updateTask - {"taskId":"<full-uuid>","status":"completed|failed","result":"summary of what was done"}
160
+ listTasks - {} or {"status":"running","parentTaskId":"<uuid>"} → list tasks with IDs and status
161
+ getTask - {"taskId":"<full-uuid>"} → full details + child tasks + sub-agent info
162
+ Statuses: pending | in_progress | completed | failed
163
+ Tasks auto-link to the current parent task. Use createTask before starting each step, updateTask when done.
164
+ When spawning sub-agents, include the task ID in their description so they can call updateTask on it.`;
@@ -19,7 +19,9 @@ export async function useMCP(serverName, taskDescription) {
19
19
  return `Access denied: MCP server "${serverName}" is not in your allowed list. Contact the operator.`;
20
20
  }
21
21
 
22
- return runMCPAgent(serverName, taskDescription);
22
+ const mainSessionId = store?.sessionId || null;
23
+ const parentTaskId = store?.currentTaskId || null;
24
+ return runMCPAgent(serverName, taskDescription, { mainSessionId, parentTaskId });
23
25
  }
24
26
 
25
27
  export const useMCPDescription =