daemora 1.0.1 → 1.0.3

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 (134) hide show
  1. package/README.md +106 -76
  2. package/SOUL.md +100 -28
  3. package/config/mcp.json +9 -9
  4. package/package.json +15 -8
  5. package/skills/apple-notes.md +0 -52
  6. package/skills/apple-reminders.md +1 -87
  7. package/skills/camsnap.md +20 -144
  8. package/skills/coding.md +7 -7
  9. package/skills/documents.md +6 -6
  10. package/skills/email.md +6 -6
  11. package/skills/gif-search.md +28 -171
  12. package/skills/healthcheck.md +21 -203
  13. package/skills/image-gen.md +24 -123
  14. package/skills/model-usage.md +18 -165
  15. package/skills/obsidian.md +28 -174
  16. package/skills/pdf.md +30 -181
  17. package/skills/research.md +6 -6
  18. package/skills/skill-creator.md +35 -111
  19. package/skills/spotify.md +2 -17
  20. package/skills/summarize.md +36 -193
  21. package/skills/things.md +23 -175
  22. package/skills/tmux.md +1 -91
  23. package/skills/trello.md +32 -157
  24. package/skills/video-frames.md +26 -166
  25. package/skills/weather.md +6 -6
  26. package/src/a2a/A2AClient.js +2 -2
  27. package/src/a2a/A2AServer.js +6 -6
  28. package/src/a2a/AgentCard.js +2 -2
  29. package/src/agents/SubAgentManager.js +61 -19
  30. package/src/agents/Supervisor.js +4 -4
  31. package/src/channels/BaseChannel.js +6 -6
  32. package/src/channels/BlueBubblesChannel.js +112 -0
  33. package/src/channels/DiscordChannel.js +8 -8
  34. package/src/channels/EmailChannel.js +54 -26
  35. package/src/channels/FeishuChannel.js +140 -0
  36. package/src/channels/GoogleChatChannel.js +8 -8
  37. package/src/channels/HttpChannel.js +2 -2
  38. package/src/channels/IRCChannel.js +144 -0
  39. package/src/channels/LineChannel.js +13 -13
  40. package/src/channels/MatrixChannel.js +97 -0
  41. package/src/channels/MattermostChannel.js +119 -0
  42. package/src/channels/NextcloudChannel.js +133 -0
  43. package/src/channels/NostrChannel.js +175 -0
  44. package/src/channels/SignalChannel.js +9 -9
  45. package/src/channels/SlackChannel.js +10 -10
  46. package/src/channels/TeamsChannel.js +10 -10
  47. package/src/channels/TelegramChannel.js +8 -8
  48. package/src/channels/TwitchChannel.js +128 -0
  49. package/src/channels/WhatsAppChannel.js +10 -10
  50. package/src/channels/ZaloChannel.js +119 -0
  51. package/src/channels/iMessageChannel.js +150 -0
  52. package/src/channels/index.js +241 -11
  53. package/src/cli.js +835 -38
  54. package/src/config/agentProfiles.js +19 -19
  55. package/src/config/channels.js +1 -1
  56. package/src/config/default.js +12 -7
  57. package/src/config/models.js +3 -3
  58. package/src/config/permissions.js +2 -2
  59. package/src/core/AgentLoop.js +13 -13
  60. package/src/core/Compaction.js +3 -3
  61. package/src/core/CostTracker.js +2 -2
  62. package/src/core/EventBus.js +15 -15
  63. package/src/core/TaskQueue.js +24 -7
  64. package/src/core/TaskRunner.js +19 -6
  65. package/src/daemon/DaemonManager.js +4 -4
  66. package/src/hooks/HookRunner.js +4 -4
  67. package/src/index.js +6 -2
  68. package/src/mcp/MCPAgentRunner.js +3 -3
  69. package/src/mcp/MCPClient.js +9 -9
  70. package/src/mcp/MCPManager.js +14 -14
  71. package/src/models/ModelRouter.js +2 -2
  72. package/src/safety/AuditLog.js +3 -3
  73. package/src/safety/CircuitBreaker.js +2 -2
  74. package/src/safety/CommandGuard.js +132 -0
  75. package/src/safety/FilesystemGuard.js +23 -3
  76. package/src/safety/GitRollback.js +5 -5
  77. package/src/safety/HumanApproval.js +9 -9
  78. package/src/safety/InputSanitizer.js +81 -8
  79. package/src/safety/PermissionGuard.js +2 -2
  80. package/src/safety/Sandbox.js +1 -1
  81. package/src/safety/SecretScanner.js +90 -28
  82. package/src/safety/SecretVault.js +2 -2
  83. package/src/scheduler/Heartbeat.js +3 -3
  84. package/src/scheduler/Scheduler.js +6 -6
  85. package/src/setup/theme.js +171 -66
  86. package/src/setup/wizard.js +432 -57
  87. package/src/skills/SkillLoader.js +145 -8
  88. package/src/storage/TaskStore.js +39 -15
  89. package/src/systemPrompt.js +45 -43
  90. package/src/tenants/TenantManager.js +79 -22
  91. package/src/tools/ToolRegistry.js +3 -3
  92. package/src/tools/applyPatch.js +2 -2
  93. package/src/tools/browserAutomation.js +4 -4
  94. package/src/tools/calendar.js +155 -0
  95. package/src/tools/clipboard.js +71 -0
  96. package/src/tools/contacts.js +138 -0
  97. package/src/tools/createDocument.js +2 -2
  98. package/src/tools/cronTool.js +14 -14
  99. package/src/tools/database.js +165 -0
  100. package/src/tools/editFile.js +10 -10
  101. package/src/tools/executeCommand.js +11 -3
  102. package/src/tools/generateImage.js +79 -0
  103. package/src/tools/gitTool.js +141 -0
  104. package/src/tools/glob.js +1 -1
  105. package/src/tools/googlePlaces.js +136 -0
  106. package/src/tools/grep.js +2 -2
  107. package/src/tools/iMessageTool.js +86 -0
  108. package/src/tools/imageAnalysis.js +3 -3
  109. package/src/tools/index.js +56 -2
  110. package/src/tools/makeVoiceCall.js +283 -0
  111. package/src/tools/manageAgents.js +2 -2
  112. package/src/tools/manageMCP.js +38 -20
  113. package/src/tools/memory.js +25 -32
  114. package/src/tools/messageChannel.js +1 -1
  115. package/src/tools/notification.js +90 -0
  116. package/src/tools/philipsHue.js +147 -0
  117. package/src/tools/projectTracker.js +8 -8
  118. package/src/tools/readFile.js +1 -1
  119. package/src/tools/readPDF.js +73 -0
  120. package/src/tools/screenCapture.js +6 -6
  121. package/src/tools/searchContent.js +2 -2
  122. package/src/tools/searchFiles.js +1 -1
  123. package/src/tools/sendEmail.js +79 -24
  124. package/src/tools/sendFile.js +4 -4
  125. package/src/tools/sonos.js +137 -0
  126. package/src/tools/sshTool.js +130 -0
  127. package/src/tools/textToSpeech.js +5 -5
  128. package/src/tools/transcribeAudio.js +4 -4
  129. package/src/tools/useMCP.js +4 -4
  130. package/src/tools/webFetch.js +2 -2
  131. package/src/tools/webSearch.js +1 -1
  132. package/src/utils/Embeddings.js +79 -0
  133. package/src/voice/VoiceSessionManager.js +170 -0
  134. package/src/voice/VoiceWebhook.js +188 -0
@@ -1,24 +1,24 @@
1
1
  /**
2
- * Agent Profiles tool presets for common sub-agent roles.
2
+ * Agent Profiles - tool presets for common sub-agent roles.
3
3
  *
4
4
  * Rather than giving every sub-agent all 33 tools, profiles provide
5
5
  * focused tool sets matched to the task type. Inspired by the research
6
6
  * finding that specialized context windows outperform bloated ones.
7
7
  *
8
8
  * Usage in spawnAgent / parallelAgents:
9
- * { profile: "coder" } preset tool list
10
- * { profile: "researcher", extraTools: ["writeFile"] } preset + additions
11
- * { tools: ["readFile", "webSearch"] } explicit list (overrides profile)
9
+ * { profile: "coder" } - preset tool list
10
+ * { profile: "researcher", extraTools: ["writeFile"] } - preset + additions
11
+ * { tools: ["readFile", "webSearch"] } - explicit list (overrides profile)
12
12
  *
13
- * spawnAgent and parallelAgents are injected dynamically based on depth not in profiles.
13
+ * spawnAgent and parallelAgents are injected dynamically based on depth - not in profiles.
14
14
  */
15
15
 
16
16
  export const agentProfiles = {
17
17
 
18
18
  /**
19
- * researcher gather, analyze, summarize, produce findings.
19
+ * researcher - gather, analyze, summarize, produce findings.
20
20
  * Reads files, searches web, fetches URLs, analyzes images. Saves findings to files.
21
- * Does NOT ask the user what to look for searches until it has enough to answer fully.
21
+ * Does NOT ask the user what to look for - searches until it has enough to answer fully.
22
22
  * Produces structured output: facts, sources, analysis, recommendations.
23
23
  */
24
24
  researcher: [
@@ -33,7 +33,7 @@ export const agentProfiles = {
33
33
  ],
34
34
 
35
35
  /**
36
- * coder build, fix, test, verify.
36
+ * coder - build, fix, test, verify.
37
37
  * Full ownership: writes code, runs builds, starts dev servers, tests UI visually,
38
38
  * writes test cases, runs them, fixes failures. Does everything without asking the user.
39
39
  */
@@ -51,9 +51,9 @@ export const agentProfiles = {
51
51
  ],
52
52
 
53
53
  /**
54
- * writer produce polished documents, reports, content.
54
+ * writer - produce polished documents, reports, content.
55
55
  * Reads existing content for context, researches via web, produces clean output.
56
- * Does NOT ask what tone/format to use unless genuinely ambiguous infers from context.
56
+ * Does NOT ask what tone/format to use unless genuinely ambiguous - infers from context.
57
57
  * Delivers the final document, not a draft asking for feedback.
58
58
  */
59
59
  writer: [
@@ -65,7 +65,7 @@ export const agentProfiles = {
65
65
  ],
66
66
 
67
67
  /**
68
- * analyst process data, run scripts, extract insights, produce output.
68
+ * analyst - process data, run scripts, extract insights, produce output.
69
69
  * Shell execution for data processing + web + vision for charts/visuals.
70
70
  * Runs scripts, parses output, draws conclusions. Delivers findings, not raw data.
71
71
  */
@@ -86,14 +86,14 @@ export const agentProfiles = {
86
86
  *
87
87
  * Covers the majority of tasks while excluding high-blast-radius tools
88
88
  * that sub-agents rarely need and that carry side effects beyond task scope:
89
- * - cron schedules recurring tasks that outlive the sub-agent
90
- * - sendEmail sub-agents shouldn't initiate email
91
- * - messageChannel sub-agents shouldn't send messages
92
- * - screenCapture sub-agents don't need screen access
93
- * - manageAgents sub-agents shouldn't kill/steer other agents
94
- * - delegateToAgent A2A from sub-agents is unpredictable
89
+ * - cron - schedules recurring tasks that outlive the sub-agent
90
+ * - sendEmail - sub-agents shouldn't initiate email
91
+ * - messageChannel - sub-agents shouldn't send messages
92
+ * - screenCapture - sub-agents don't need screen access
93
+ * - manageAgents - sub-agents shouldn't kill/steer other agents
94
+ * - delegateToAgent - A2A from sub-agents is unpredictable
95
95
  *
96
- * spawnAgent and parallelAgents are NOT listed here they are injected
96
+ * spawnAgent and parallelAgents are NOT listed here - they are injected
97
97
  * dynamically into sub-agents by SubAgentManager based on recursion depth.
98
98
  */
99
99
  export const defaultSubAgentTools = [
@@ -114,7 +114,7 @@ export const defaultSubAgentTools = [
114
114
  "searchMemory", "pruneMemory", "listMemoryCategories",
115
115
  // Project tracking
116
116
  "projectTracker",
117
- // MCP (via specialist agent no direct mcp__ tools)
117
+ // MCP (via specialist agent - no direct mcp__ tools)
118
118
  "manageMCP",
119
119
  "useMCP",
120
120
  ];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Channel configuration which input channels are enabled.
2
+ * Channel configuration - which input channels are enabled.
3
3
  * Actual credentials come from .env via config/default.js.
4
4
  */
5
5
  export const channelDefaults = {
@@ -87,7 +87,7 @@ export const config = {
87
87
  isolateFilesystem: process.env.TENANT_ISOLATE_FILESYSTEM === "true",
88
88
  },
89
89
 
90
- // Sandbox OS-level command isolation
90
+ // Sandbox - OS-level command isolation
91
91
  // "process" (default): commands run in the current process, tool-level path guards apply.
92
92
  // "docker": commands run inside a Docker container, providing kernel-level isolation.
93
93
  // Requires Docker installed. Container gets no network by default (DOCKER_NETWORK=none).
@@ -101,12 +101,12 @@ export const config = {
101
101
 
102
102
  // Channels
103
103
  // Each channel supports two universal options:
104
- // allowlist comma-separated IDs/numbers/usernames in env var (e.g. TELEGRAM_ALLOWLIST="123456789,987654321")
104
+ // allowlist - comma-separated IDs/numbers/usernames in env var (e.g. TELEGRAM_ALLOWLIST="123456789,987654321")
105
105
  // If empty or not set → open to everyone. Set this to lock down your bot.
106
- // model per-channel model override (e.g. TELEGRAM_MODEL="anthropic:claude-opus-4-6")
106
+ // model - per-channel model override (e.g. TELEGRAM_MODEL="anthropic:claude-opus-4-6")
107
107
  // If not set → global DEFAULT_MODEL is used.
108
108
  channels: {
109
- // HTTP channel is disabled unauthenticated, see src/channels/index.js
109
+ // HTTP channel is disabled - unauthenticated, see src/channels/index.js
110
110
  http: { enabled: false },
111
111
 
112
112
  telegram: {
@@ -130,7 +130,12 @@ export const config = {
130
130
  },
131
131
 
132
132
  email: {
133
- enabled: !!process.env.EMAIL_USER,
133
+ // Enabled if EITHER Resend (outbound) OR Gmail/SMTP (full) is configured
134
+ enabled: !!(process.env.RESEND_API_KEY || process.env.EMAIL_USER),
135
+ // Resend (recommended - just an API key for outbound sending)
136
+ resendApiKey: process.env.RESEND_API_KEY || null,
137
+ resendFrom: process.env.RESEND_FROM || null,
138
+ // Traditional IMAP/SMTP (needed for inbox polling / inbound)
134
139
  imap: {
135
140
  host: process.env.EMAIL_IMAP_HOST || "imap.gmail.com",
136
141
  port: parseInt(process.env.EMAIL_IMAP_PORT || "993", 10),
@@ -139,8 +144,8 @@ export const config = {
139
144
  host: process.env.EMAIL_SMTP_HOST || "smtp.gmail.com",
140
145
  port: parseInt(process.env.EMAIL_SMTP_PORT || "587", 10),
141
146
  },
142
- user: process.env.EMAIL_USER,
143
- password: process.env.EMAIL_PASSWORD,
147
+ user: process.env.EMAIL_USER || null,
148
+ password: process.env.EMAIL_PASSWORD || null,
144
149
  allowlist: process.env.EMAIL_ALLOWLIST
145
150
  ? process.env.EMAIL_ALLOWLIST.split(",").map((s) => s.trim()).filter(Boolean)
146
151
  : [],
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Model registry metadata for all supported models.
2
+ * Model registry - metadata for all supported models.
3
3
  * Used by ModelRouter for selection, cost tracking, and compaction thresholds.
4
4
  */
5
5
  export const models = {
@@ -79,7 +79,7 @@ export const models = {
79
79
  tier: "cheap",
80
80
  },
81
81
 
82
- // Ollama (local no cost)
82
+ // Ollama (local - no cost)
83
83
  "ollama:llama3": {
84
84
  provider: "ollama",
85
85
  model: "llama3",
@@ -113,7 +113,7 @@ export const models = {
113
113
  };
114
114
 
115
115
  /**
116
- * Fallback chains if preferred model fails, try next in chain.
116
+ * Fallback chains - if preferred model fails, try next in chain.
117
117
  */
118
118
  export const fallbackChains = {
119
119
  cheap: ["openai:gpt-4.1-mini", "anthropic:claude-haiku-4-5", "google:gemini-2.0-flash"],
@@ -6,7 +6,7 @@
6
6
  export const permissionTiers = {
7
7
  minimal: {
8
8
  name: "Minimal (Read-Only)",
9
- description: "Agent can read files, search, and browse the web no writes, no shell, no communication.",
9
+ description: "Agent can read files, search, and browse the web - no writes, no shell, no communication.",
10
10
  allowedTools: [
11
11
  // File reads
12
12
  "readFile",
@@ -60,7 +60,7 @@ export const permissionTiers = {
60
60
  "screenCapture",
61
61
  // Project tracking
62
62
  "projectTracker",
63
- // MCP inspection (read-only no side effects)
63
+ // MCP inspection (read-only - no side effects)
64
64
  "manageMCP",
65
65
  ],
66
66
  },
@@ -13,7 +13,7 @@ import supervisor from "../agents/Supervisor.js";
13
13
  import gitRollback from "../safety/GitRollback.js";
14
14
 
15
15
  /**
16
- * Core agent loop model-agnostic via Vercel AI SDK.
16
+ * Core agent loop - model-agnostic via Vercel AI SDK.
17
17
  *
18
18
  * Extracted from the original openai.js. This is the brain of the agent:
19
19
  * 1. Send messages to model (any provider)
@@ -38,14 +38,14 @@ export async function runAgentLoop({
38
38
  taskId = null,
39
39
  approvalMode = "auto", // "auto" | "dangerous-only" | "every-tool"
40
40
  channelMeta = null, // passed through to HumanApproval so channel can notify user
41
- signal = null, // AbortController.signal hard-kills the loop mid-call
42
- steerQueue = null, // shared mutable array push strings here to steer the agent
43
- apiKeys = {}, // per-tenant API key overlay passed through to provider factory
41
+ signal = null, // AbortController.signal - hard-kills the loop mid-call
42
+ steerQueue = null, // shared mutable array - push strings here to steer the agent
43
+ apiKeys = {}, // per-tenant API key overlay - passed through to provider factory
44
44
  }) {
45
45
  const selectedModelId = modelId || config.defaultModel;
46
46
  const { model, meta, modelId: resolvedModelId } = getModelWithFallback(selectedModelId, apiKeys);
47
47
 
48
- // Build set of known secret values to redact from tool outputs (dynamic catches tenant keys)
48
+ // Build set of known secret values to redact from tool outputs (dynamic - catches tenant keys)
49
49
  const _knownSecrets = new Set([
50
50
  ...Object.values(apiKeys),
51
51
  process.env.OPENAI_API_KEY,
@@ -111,7 +111,7 @@ export async function runAgentLoop({
111
111
  while (steerQueue.length > 0) {
112
112
  const item = steerQueue.shift();
113
113
  if (item && typeof item === "object" && item.type === "user") {
114
- // User sent a follow-up mid-task inject as a natural user turn
114
+ // User sent a follow-up mid-task - inject as a natural user turn
115
115
  console.log(`[AgentLoop] User follow-up injected: "${item.content.slice(0, 80)}"`);
116
116
  messages.push({ role: "user", content: item.content });
117
117
  } else {
@@ -259,7 +259,7 @@ export async function runAgentLoop({
259
259
  continue;
260
260
  }
261
261
 
262
- // Git snapshot before the first write tool in this task
262
+ // Git snapshot - before the first write tool in this task
263
263
  if (!gitSnapshotDone && WRITE_TOOLS.has(tool_name)) {
264
264
  gitRollback.snapshot(taskId);
265
265
  gitSnapshotDone = true;
@@ -331,7 +331,7 @@ export async function runAgentLoop({
331
331
  });
332
332
  }
333
333
  } else {
334
- console.log(`[Step ${stepCount}] Unknown tool: ${tool_name} skipping`);
334
+ console.log(`[Step ${stepCount}] Unknown tool: ${tool_name} - skipping`);
335
335
  messages.push({
336
336
  role: "user",
337
337
  content: JSON.stringify({
@@ -347,7 +347,7 @@ export async function runAgentLoop({
347
347
  // --- Final response handling ---
348
348
  if (parsedOutput.finalResponse || parsedOutput.type === "text") {
349
349
  if (!parsedOutput.text_content) {
350
- console.log(`[Loop ${loopCount}] Model signaled done but text_content is null asking for summary`);
350
+ console.log(`[Loop ${loopCount}] Model signaled done but text_content is null - asking for summary`);
351
351
  messages.push({
352
352
  role: "user",
353
353
  content:
@@ -363,7 +363,7 @@ export async function runAgentLoop({
363
363
  const lastUserMsg = msgs[msgs.length - 1]?.content?.toLowerCase() || "";
364
364
  const isAck = /^(ok|okay|yes|yeah|sure|thanks|thank you|no|nah|k|yep|yup|got it|cool|nice|great|good|alright|👍)\.?$/i.test(lastUserMsg.trim());
365
365
  if (!isAck && lastUserMsg.length > 5) {
366
- console.log(`[Loop ${loopCount}] LAZY MODEL DETECTED claimed done but used 0 tools. Forcing tool use.`);
366
+ console.log(`[Loop ${loopCount}] LAZY MODEL DETECTED - claimed done but used 0 tools. Forcing tool use.`);
367
367
  messages.push({
368
368
  role: "user",
369
369
  content: `You responded without using any tools. You MUST actually use tools (readFile, editFile, writeFile, executeCommand, etc.) to complete the task. Do NOT claim you fixed or changed something without actually doing it. Use your tools NOW to fulfill the request.`,
@@ -375,10 +375,10 @@ export async function runAgentLoop({
375
375
  // SAFEGUARD 2: Model used only READ tools but claims it modified/fixed something
376
376
  // If the response contains action words but no write tool was ever called, push back.
377
377
  if (!writeToolUsed && stepCount > 0 && loopCount <= 4 && ACTION_WORDS.test(parsedOutput.text_content)) {
378
- console.log(`[Loop ${loopCount}] PHANTOM WRITE DETECTED model claims "${parsedOutput.text_content.slice(0, 80)}..." but only used read tools. Forcing actual writes.`);
378
+ console.log(`[Loop ${loopCount}] PHANTOM WRITE DETECTED - model claims "${parsedOutput.text_content.slice(0, 80)}..." but only used read tools. Forcing actual writes.`);
379
379
  messages.push({
380
380
  role: "user",
381
- content: `You claim to have made changes but you only used read tools you never called writeFile or editFile to actually modify any file. The files are UNCHANGED. You must use writeFile or editFile to actually make the changes. Do it now.`,
381
+ content: `You claim to have made changes but you only used read tools - you never called writeFile or editFile to actually modify any file. The files are UNCHANGED. You must use writeFile or editFile to actually make the changes. Do it now.`,
382
382
  });
383
383
  continue;
384
384
  }
@@ -406,7 +406,7 @@ export async function runAgentLoop({
406
406
  return { text: parsedOutput.text_content, messages: conversationMessages, cost };
407
407
  }
408
408
  } catch (error) {
409
- // Abort signal fires as an error exit cleanly
409
+ // Abort signal fires as an error - exit cleanly
410
410
  if (signal?.aborted || error.name === "AbortError") {
411
411
  console.log(`[Loop ${loopCount}] Aborted mid-call.`);
412
412
  return {
@@ -28,7 +28,7 @@ export function estimateTokens(messages) {
28
28
  }
29
29
 
30
30
  /**
31
- * Prune a single tool output truncate if too long.
31
+ * Prune a single tool output - truncate if too long.
32
32
  */
33
33
  function pruneToolOutput(content, maxChars = 5000) {
34
34
  if (typeof content !== "string") content = JSON.stringify(content);
@@ -52,7 +52,7 @@ function persistLargeOutput(content, taskId, stepIndex) {
52
52
  const filename = `${taskId || "unknown"}-step${stepIndex}-${Date.now()}.txt`;
53
53
  const filePath = `${dir}/${filename}`;
54
54
  writeFileSync(filePath, content);
55
- return `[Output saved to disk: ${filePath} ${content.length} chars]`;
55
+ return `[Output saved to disk: ${filePath} - ${content.length} chars]`;
56
56
  }
57
57
 
58
58
  /**
@@ -82,7 +82,7 @@ export async function compactIfNeeded(messages, modelMeta, taskId = null) {
82
82
  const oldMessages = messages.slice(1, -recentCount);
83
83
 
84
84
  if (oldMessages.length === 0) {
85
- // Nothing to compact all messages are recent
85
+ // Nothing to compact - all messages are recent
86
86
  return messages;
87
87
  }
88
88
 
@@ -33,7 +33,7 @@ export function logCost({ taskId, modelId, inputTokens, outputTokens, estimatedC
33
33
  }
34
34
 
35
35
  /**
36
- * Get total cost spent today (global all tenants combined).
36
+ * Get total cost spent today (global - all tenants combined).
37
37
  */
38
38
  export function getTodayCost() {
39
39
  const logPath = getTodayLogPath();
@@ -101,7 +101,7 @@ export function estimateCost(modelId, inputTokens, outputTokens) {
101
101
  return (inputTokens / 1000) * meta.costPer1kInput + (outputTokens / 1000) * meta.costPer1kOutput;
102
102
  }
103
103
 
104
- // Auto-log costs from EventBus includes tenantId from TenantContext
104
+ // Auto-log costs from EventBus - includes tenantId from TenantContext
105
105
  eventBus.on("model:called", (data) => {
106
106
  const tenantId = tenantContext.getStore()?.tenant?.id || null;
107
107
  const cost = estimateCost(data.modelId, data.inputTokens || 0, data.outputTokens || 0);
@@ -4,21 +4,21 @@ import { EventEmitter } from "events";
4
4
  * Global event bus for inter-module communication.
5
5
  *
6
6
  * Events:
7
- * task:created new task enqueued
8
- * task:started task picked up by runner
9
- * task:completed task finished successfully
10
- * task:failed task failed
11
- * tool:before about to execute a tool (PreToolUse hook point)
12
- * tool:after tool execution finished (PostToolUse hook point)
13
- * tool:blocked tool call was blocked by safety
14
- * agent:spawned sub-agent created
15
- * agent:finished sub-agent completed
16
- * agent:killed sub-agent terminated by supervisor
17
- * model:called LLM API call made (for cost tracking)
18
- * compact:triggered context compaction started
19
- * memory:written memory entry added
20
- * secret:detected secret found and redacted
21
- * audit:event generic audit event
7
+ * task:created - new task enqueued
8
+ * task:started - task picked up by runner
9
+ * task:completed - task finished successfully
10
+ * task:failed - task failed
11
+ * tool:before - about to execute a tool (PreToolUse hook point)
12
+ * tool:after - tool execution finished (PostToolUse hook point)
13
+ * tool:blocked - tool call was blocked by safety
14
+ * agent:spawned - sub-agent created
15
+ * agent:finished - sub-agent completed
16
+ * agent:killed - sub-agent terminated by supervisor
17
+ * model:called - LLM API call made (for cost tracking)
18
+ * compact:triggered - context compaction started
19
+ * memory:written - memory entry added
20
+ * secret:detected - secret found and redacted
21
+ * audit:event - generic audit event
22
22
  */
23
23
  class AgentEventBus extends EventEmitter {
24
24
  constructor() {
@@ -1,5 +1,5 @@
1
1
  import { createTask, startTask, completeTask, failTask } from "./Task.js";
2
- import { saveTask, loadTask, recoverStaleTasks } from "../storage/TaskStore.js";
2
+ import { saveTask, loadTask, recoverStaleTasks, loadPendingTasks } from "../storage/TaskStore.js";
3
3
  import eventBus from "./EventBus.js";
4
4
 
5
5
  /**
@@ -21,11 +21,24 @@ class TaskQueue {
21
21
  }
22
22
 
23
23
  /**
24
- * Initialize queue recover any stale tasks from previous crash.
24
+ * Initialize queue - recover stale tasks from a previous crash/restart and
25
+ * re-hydrate the in-memory queue so they execute automatically without human
26
+ * re-input. Tasks that were "running" when the process died are reset to
27
+ * "pending" on disk first, then all pending tasks are loaded back into queue.
25
28
  */
26
29
  init() {
27
- recoverStaleTasks();
28
- console.log(`[TaskQueue] Initialized`);
30
+ const recovered = recoverStaleTasks();
31
+
32
+ const pending = loadPendingTasks();
33
+ for (const task of pending) {
34
+ this.queue.push(task);
35
+ }
36
+
37
+ if (pending.length > 0) {
38
+ console.log(`[TaskQueue] Requeued ${pending.length} pending task(s) from previous session (${recovered} recovered from crash)`);
39
+ } else {
40
+ console.log(`[TaskQueue] Initialized (no pending tasks from previous session)`);
41
+ }
29
42
  }
30
43
 
31
44
  /**
@@ -91,11 +104,15 @@ class TaskQueue {
91
104
 
92
105
  eventBus.emitEvent("task:completed", { taskId: task.id, cost: task.cost });
93
106
 
94
- // Resolve any sync waiters
107
+ // Resolve any sync waiters (normal flow - channel is waiting for completion)
95
108
  const waiter = this.waiters.get(taskId);
96
109
  if (waiter) {
97
110
  waiter.resolve(task);
98
111
  this.waiters.delete(taskId);
112
+ } else if (task.channel && task.channel !== "http" && task.channel !== "a2a") {
113
+ // No waiter = recovered task (agent restarted while task was in-flight).
114
+ // Emit so ChannelRegistry can route the reply back to the user automatically.
115
+ eventBus.emitEvent("task:reply:needed", { task });
99
116
  }
100
117
 
101
118
  return task;
@@ -117,7 +134,7 @@ class TaskQueue {
117
134
  // Reject any sync waiters
118
135
  const waiter = this.waiters.get(taskId);
119
136
  if (waiter) {
120
- waiter.resolve(task); // resolve not reject caller handles error state
137
+ waiter.resolve(task); // resolve not reject - caller handles error state
121
138
  this.waiters.delete(taskId);
122
139
  }
123
140
 
@@ -137,7 +154,7 @@ class TaskQueue {
137
154
  return;
138
155
  }
139
156
 
140
- // Timeout guard prevent hanging forever (default 5 min)
157
+ // Timeout guard - prevent hanging forever (default 5 min)
141
158
  const timer = setTimeout(() => {
142
159
  this.waiters.delete(taskId);
143
160
  resolve({
@@ -7,9 +7,10 @@ import { isDailyBudgetExceeded, isTenantDailyBudgetExceeded } from "./CostTracke
7
7
  import { config } from "../config/default.js";
8
8
  import tenantManager from "../tenants/TenantManager.js";
9
9
  import tenantContext from "../tenants/TenantContext.js";
10
+ import inputSanitizer from "../safety/InputSanitizer.js";
10
11
 
11
12
  /**
12
- * Task runner worker loop that picks tasks from the queue and executes them.
13
+ * Task runner - worker loop that picks tasks from the queue and executes them.
13
14
  *
14
15
  * Configurable concurrency (default: 2 parallel tasks).
15
16
  */
@@ -48,7 +49,7 @@ class TaskRunner {
48
49
  }
49
50
 
50
51
  /**
51
- * Poll tick pick up work if available and under concurrency limit.
52
+ * Poll tick - pick up work if available and under concurrency limit.
52
53
  */
53
54
  tick() {
54
55
  if (!this.running) return;
@@ -57,13 +58,13 @@ class TaskRunner {
57
58
  // ── Steer/inject: if next task belongs to an already-running session,
58
59
  // append its message into the live agent loop rather than spawning a
59
60
  // second loop. The agent picks it up between tool calls (like Claude Code).
60
- // This runs regardless of concurrency no extra slot needed.
61
+ // This runs regardless of concurrency - no extra slot needed.
61
62
  const nextTask = taskQueue.peek();
62
63
  if (nextTask?.sessionId && this.sessionSteerQueues.has(nextTask.sessionId)) {
63
64
  const steerTask = taskQueue.dequeue(); // consume from queue
64
65
  const steerQueue = this.sessionSteerQueues.get(steerTask.sessionId);
65
66
  steerQueue.push({ type: "user", content: steerTask.input }); // inject into live loop
66
- taskQueue.merge(steerTask.id); // complete silently no duplicate reply
67
+ taskQueue.merge(steerTask.id); // complete silently - no duplicate reply
67
68
  console.log(`[TaskRunner] Follow-up "${steerTask.input.slice(0, 60)}" injected into running session ${steerTask.sessionId}`);
68
69
  return;
69
70
  }
@@ -106,6 +107,18 @@ class TaskRunner {
106
107
  async processTask(task, steerQueue = []) {
107
108
  console.log(`\n[TaskRunner] Processing task ${task.id} from ${task.channel}`);
108
109
 
110
+ // ── Prompt injection detection ─────────────────────────────────────────────
111
+ // Check user input for jailbreak / credential-extraction attempts.
112
+ // We don't block — we prepend a SECURITY_NOTICE so the agent is warned.
113
+ // Channel-sourced tasks only (not http/a2a which have their own auth).
114
+ if (task.input && task.channel && task.channel !== "http" && task.channel !== "a2a") {
115
+ const injCheck = inputSanitizer.detectInjection(task.input);
116
+ if (injCheck.suspicious) {
117
+ task.input = injCheck.warningPrefix + task.input;
118
+ }
119
+ }
120
+ // ──────────────────────────────────────────────────────────────────────────
121
+
109
122
  // ── Multi-tenant: resolve tenant and effective config ──────────────────────
110
123
  // Derive userId from sessionId: sessionId = "${channel}-${userId}"
111
124
  let tenant = null;
@@ -121,7 +134,7 @@ class TaskRunner {
121
134
  const reason = tenant.suspendReason
122
135
  ? `Account suspended: ${tenant.suspendReason}`
123
136
  : "Account suspended. Contact the operator.";
124
- console.log(`[TaskRunner] Task ${task.id} rejected tenant ${tenant.id} is suspended`);
137
+ console.log(`[TaskRunner] Task ${task.id} rejected - tenant ${tenant.id} is suspended`);
125
138
  taskQueue.fail(task.id, reason);
126
139
  return;
127
140
  }
@@ -132,7 +145,7 @@ class TaskRunner {
132
145
  // Per-tenant daily budget check (separate from global budget)
133
146
  if (tenant && resolvedConfig.maxDailyCost) {
134
147
  if (isTenantDailyBudgetExceeded(tenant.id, resolvedConfig.maxDailyCost)) {
135
- console.log(`[TaskRunner] Task ${task.id} rejected tenant ${tenant.id} daily budget ($${resolvedConfig.maxDailyCost}) reached`);
148
+ console.log(`[TaskRunner] Task ${task.id} rejected - tenant ${tenant.id} daily budget ($${resolvedConfig.maxDailyCost}) reached`);
136
149
  taskQueue.fail(task.id, `Daily budget of $${resolvedConfig.maxDailyCost} reached. Tasks resume tomorrow.`);
137
150
  return;
138
151
  }
@@ -9,11 +9,11 @@ const SERVICE_NAME = "daemora-agent";
9
9
  const SERVICE_LABEL = "com.daemora.agent";
10
10
 
11
11
  /**
12
- * Daemon Manager native OS service management.
12
+ * Daemon Manager - native OS service management.
13
13
  *
14
14
  * Like OpenClaw: uses the OS's native service system, NOT pm2.
15
- * - macOS: LaunchAgent (launchctl) ~/Library/LaunchAgents/
16
- * - Linux: systemd user service ~/.config/systemd/user/
15
+ * - macOS: LaunchAgent (launchctl) - ~/Library/LaunchAgents/
16
+ * - Linux: systemd user service - ~/.config/systemd/user/
17
17
  * - Windows: Scheduled Task (schtasks)
18
18
  *
19
19
  * Features:
@@ -204,7 +204,7 @@ export class DaemonManager {
204
204
  const unitPath = join(unitDir, `${SERVICE_NAME}.service`);
205
205
 
206
206
  const unit = `[Unit]
207
- Description=Daemora 24/7 AI Digital Worker
207
+ Description=Daemora - 24/7 AI Digital Worker
208
208
  After=network-online.target
209
209
  Wants=network-online.target
210
210
 
@@ -5,7 +5,7 @@ import { config } from "../config/default.js";
5
5
  import eventBus from "../core/EventBus.js";
6
6
 
7
7
  /**
8
- * Hook Runner event-driven interception at tool lifecycle points.
8
+ * Hook Runner - event-driven interception at tool lifecycle points.
9
9
  * Inspired by Claude Code's hook system.
10
10
  *
11
11
  * Supports:
@@ -46,7 +46,7 @@ class HookRunner {
46
46
  // Create default hooks file
47
47
  this.hooks = {};
48
48
  this.loaded = true;
49
- console.log(`[HookRunner] No hooks.json found hooks disabled`);
49
+ console.log(`[HookRunner] No hooks.json found - hooks disabled`);
50
50
  return;
51
51
  }
52
52
 
@@ -110,7 +110,7 @@ class HookRunner {
110
110
  console.log(
111
111
  `[HookRunner] Hook error (${event}/${hook.matcher}): ${error.message}`
112
112
  );
113
- // Hook errors don't block execution fail open
113
+ // Hook errors don't block execution - fail open
114
114
  }
115
115
  }
116
116
 
@@ -172,7 +172,7 @@ class HookRunner {
172
172
  return { decision: "allow", output };
173
173
  }
174
174
  } catch (error) {
175
- // Command failed treat as allow (fail open)
175
+ // Command failed - treat as allow (fail open)
176
176
  return { decision: "allow", error: error.message };
177
177
  }
178
178
  }
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ import scheduler from "./scheduler/Scheduler.js";
18
18
  import heartbeat from "./scheduler/Heartbeat.js";
19
19
  import { mountAgentCard } from "./a2a/AgentCard.js";
20
20
  import { mountA2AServer } from "./a2a/A2AServer.js";
21
+ import voiceWebhook from "./voice/VoiceWebhook.js";
21
22
  import daemonManager from "./daemon/DaemonManager.js";
22
23
  import secretVault from "./safety/SecretVault.js";
23
24
  import tenantManager from "./tenants/TenantManager.js";
@@ -51,6 +52,9 @@ app.use(express.json());
51
52
  mountAgentCard(app);
52
53
  mountA2AServer(app);
53
54
 
55
+ // Mount voice call webhooks (Twilio callbacks during live calls)
56
+ app.use("/voice", voiceWebhook);
57
+
54
58
  // --- Health check ---
55
59
  app.get("/health", (req, res) => {
56
60
  res.json({
@@ -65,11 +69,11 @@ app.get("/health", (req, res) => {
65
69
  });
66
70
  });
67
71
 
68
- // --- Chat endpoint (DISABLED no authentication, anyone on the network could submit tasks) ---
72
+ // --- Chat endpoint (DISABLED - no authentication, anyone on the network could submit tasks) ---
69
73
  // Uncomment and add authentication middleware before re-enabling.
70
74
  // app.post("/chat", async (req, res) => { ... });
71
75
 
72
- // --- Task submit endpoint (DISABLED same reason, unauthenticated) ---
76
+ // --- Task submit endpoint (DISABLED - same reason, unauthenticated) ---
73
77
  // app.post("/tasks", (req, res) => { ... });
74
78
 
75
79
  app.get("/tasks/:id", (req, res) => {
@@ -2,7 +2,7 @@ import { spawnSubAgent } from "../agents/SubAgentManager.js";
2
2
  import mcpManager from "./MCPManager.js";
3
3
 
4
4
  /**
5
- * MCP Agent Runner spawns specialist sub-agents for individual MCP servers.
5
+ * MCP Agent Runner - spawns specialist sub-agents for individual MCP servers.
6
6
  *
7
7
  * Each specialist gets:
8
8
  * - ONLY the tools from its assigned MCP server (no built-in tools)
@@ -49,7 +49,7 @@ All MCP tool params must be passed as a single JSON string (the first and only a
49
49
  tool_name: "mcp__${serverName}__someToolName"
50
50
  params: ['{"param1":"value1","param2":"value2"}']
51
51
 
52
- # Rules You Own This Task
52
+ # Rules - You Own This Task
53
53
 
54
54
  - **Do the work, don't describe it.** Your first response must be a tool_call, not a plan.
55
55
  - **Chain calls until fully done.** After each tool result, decide: need more tools? Call another. Only set finalResponse true when the task is genuinely complete.
@@ -106,7 +106,7 @@ export async function runMCPAgent(serverName, taskDescription, options = {}) {
106
106
  ...options,
107
107
  toolOverride: serverTools,
108
108
  systemPromptOverride,
109
- // MCP agents are always depth 1 they don't spawn further sub-agents
109
+ // MCP agents are always depth 1 - they don't spawn further sub-agents
110
110
  depth: 1,
111
111
  });
112
112
  }