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,15 +1,19 @@
1
- import { readFileSync, readdirSync, existsSync, statSync, watchFile } from "fs";
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "fs";
2
2
  import { join } from "path";
3
+ import { createHash } from "node:crypto";
3
4
  import { config } from "../config/default.js";
5
+ import { generateEmbedding, getEmbeddingProvider } from "../utils/Embeddings.js";
4
6
 
5
7
  /**
6
- * Skill Loader auto-discovers .md skill files from the skills/ directory.
8
+ * Skill Loader - auto-discovers .md skill files from the skills/ directory.
7
9
  *
8
10
  * Each skill file has YAML frontmatter (name, description, triggers)
9
11
  * followed by the skill prompt content.
10
12
  *
11
- * Skills are injected into the system prompt when their description
12
- * matches the current task context.
13
+ * Skills are matched to tasks via:
14
+ * 1. Semantic (embeddings) - cosine similarity on OpenAI text-embedding-3-small vectors.
15
+ * Vectors are cached in data/skill-embeddings.json, recomputed only when skill content changes.
16
+ * 2. Keyword fallback - simple trigger/description word matching when no API key is set.
13
17
  *
14
18
  * Format:
15
19
  * ```markdown
@@ -22,10 +26,14 @@ import { config } from "../config/default.js";
22
26
  * ```
23
27
  */
24
28
 
29
+ const SKILL_EMBEDDINGS_PATH = join(config.dataDir, "skill-embeddings.json");
30
+ const EMBED_THRESHOLD = 0.32; // Lower than memory (0.40) - skill descriptions are shorter
31
+
25
32
  class SkillLoader {
26
33
  constructor() {
27
34
  this.skills = new Map();
28
35
  this.loaded = false;
36
+ this._skillVectors = {}; // { skillName: { hash, vector } }
29
37
  }
30
38
 
31
39
  /**
@@ -56,6 +64,7 @@ class SkillLoader {
56
64
  }
57
65
 
58
66
  this.loaded = true;
67
+ this._loadSkillVectors();
59
68
  console.log(
60
69
  `[SkillLoader] Loaded ${this.skills.size} skills: ${[...this.skills.keys()].join(", ") || "(none)"}`
61
70
  );
@@ -67,7 +76,6 @@ class SkillLoader {
67
76
  parseSkill(content, filename) {
68
77
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
69
78
  if (!fmMatch) {
70
- // No frontmatter — use filename as name
71
79
  return {
72
80
  name: filename.replace(".md", ""),
73
81
  description: "",
@@ -100,6 +108,137 @@ class SkillLoader {
100
108
  };
101
109
  }
102
110
 
111
+ // ── Embedding helpers ────────────────────────────────────────────────────────
112
+
113
+ _contentHash(skill) {
114
+ // Include provider so cache auto-invalidates when user switches embedding provider
115
+ const provider = getEmbeddingProvider() || "none";
116
+ return createHash("sha1")
117
+ .update(`${provider}|${skill.name}|${skill.description}|${skill.triggers.join(",")}|${skill.content}`)
118
+ .digest("hex")
119
+ .slice(0, 16);
120
+ }
121
+
122
+ _loadSkillVectors() {
123
+ if (!existsSync(SKILL_EMBEDDINGS_PATH)) return;
124
+ try {
125
+ this._skillVectors = JSON.parse(readFileSync(SKILL_EMBEDDINGS_PATH, "utf-8"));
126
+ } catch {
127
+ this._skillVectors = {};
128
+ }
129
+ }
130
+
131
+ _saveSkillVectors() {
132
+ try {
133
+ mkdirSync(config.dataDir, { recursive: true });
134
+ writeFileSync(SKILL_EMBEDDINGS_PATH, JSON.stringify(this._skillVectors));
135
+ } catch {}
136
+ }
137
+
138
+ _generateEmbedding(text) {
139
+ return generateEmbedding(text);
140
+ }
141
+
142
+ _cosineSim(a, b) {
143
+ let dot = 0, na = 0, nb = 0;
144
+ for (let i = 0; i < a.length; i++) {
145
+ dot += a[i] * b[i];
146
+ na += a[i] * a[i];
147
+ nb += b[i] * b[i];
148
+ }
149
+ if (!na || !nb) return 0;
150
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
151
+ }
152
+
153
+ // ── Public async API ─────────────────────────────────────────────────────────
154
+
155
+ /**
156
+ * Pre-compute and cache embeddings for all loaded skills.
157
+ * Called once at startup as fire-and-forget. Only re-embeds skills whose content changed.
158
+ */
159
+ async embedSkills() {
160
+ if (!getEmbeddingProvider()) return;
161
+ if (!this.loaded) this.load();
162
+
163
+ let changed = false;
164
+
165
+ for (const [name, skill] of this.skills) {
166
+ const hash = this._contentHash(skill);
167
+ if (this._skillVectors[name]?.hash === hash) continue; // Cache hit - skip
168
+
169
+ // Text to embed: name + description + triggers + first 500 chars of body
170
+ const text = [
171
+ `Skill: ${skill.name}`,
172
+ `Description: ${skill.description}`,
173
+ `Triggers: ${skill.triggers.join(", ")}`,
174
+ skill.content.slice(0, 500),
175
+ ].join("\n");
176
+
177
+ const vector = await this._generateEmbedding(text);
178
+ if (vector) {
179
+ this._skillVectors[name] = { hash, vector };
180
+ changed = true;
181
+ console.log(`[SkillLoader] Embedded skill: ${name}`);
182
+ }
183
+ }
184
+
185
+ // Remove cached vectors for skills that no longer exist
186
+ for (const name of Object.keys(this._skillVectors)) {
187
+ if (!this.skills.has(name)) {
188
+ delete this._skillVectors[name];
189
+ changed = true;
190
+ }
191
+ }
192
+
193
+ if (changed) this._saveSkillVectors();
194
+ }
195
+
196
+ /**
197
+ * Semantic skill matching - embeds the task input and returns skills above cosine threshold.
198
+ * Falls back to keyword matching if OPENAI_API_KEY is absent or embeddings aren't ready.
199
+ *
200
+ * Returns top-5 matched skills, sorted by relevance score (highest first).
201
+ */
202
+ async getSkillPromptsAsync(taskInput) {
203
+ if (!taskInput) return "";
204
+ if (!this.loaded) this.load();
205
+
206
+ const vectorsAvailable = Object.keys(this._skillVectors).length > 0;
207
+
208
+ if (getEmbeddingProvider() && vectorsAvailable) {
209
+ const queryVector = await this._generateEmbedding(taskInput);
210
+ if (queryVector) {
211
+ const scored = [];
212
+
213
+ for (const [name, skill] of this.skills) {
214
+ const cached = this._skillVectors[name];
215
+ if (!cached?.vector) continue;
216
+ const score = this._cosineSim(queryVector, cached.vector);
217
+ if (score >= EMBED_THRESHOLD) {
218
+ scored.push({ skill, score });
219
+ }
220
+ }
221
+
222
+ scored.sort((a, b) => b.score - a.score);
223
+ const matched = scored.slice(0, 5).map((s) => s.skill);
224
+
225
+ if (matched.length > 0) {
226
+ console.log(
227
+ `[SkillLoader] Semantic match (${matched.length}): ${matched.map((s) => s.name).join(", ")}`
228
+ );
229
+ return matched
230
+ .map((s) => `\n--- Skill: ${s.name} ---\n${s.content}\n--- End Skill ---`)
231
+ .join("\n");
232
+ }
233
+ }
234
+ }
235
+
236
+ // Fallback: keyword matching
237
+ return this.getSkillPrompts(taskInput);
238
+ }
239
+
240
+ // ── Sync keyword API (fallback) ───────────────────────────────────────────────
241
+
103
242
  /**
104
243
  * Match skills relevant to a given task/message.
105
244
  * Returns skill contents to inject into the system prompt.
@@ -111,10 +250,8 @@ class SkillLoader {
111
250
  const matched = [];
112
251
 
113
252
  for (const [name, skill] of this.skills) {
114
- // Check triggers
115
253
  const triggerMatch = skill.triggers.some((t) => input.includes(t));
116
254
 
117
- // Check description keywords
118
255
  const descWords = skill.description.toLowerCase().split(/\s+/);
119
256
  const descMatch = descWords.some(
120
257
  (w) => w.length > 3 && input.includes(w)
@@ -129,7 +266,7 @@ class SkillLoader {
129
266
  }
130
267
 
131
268
  /**
132
- * Get skill prompt text for matched skills.
269
+ * Get skill prompt text for matched skills (sync keyword version).
133
270
  */
134
271
  getSkillPrompts(taskInput) {
135
272
  const matched = this.matchSkills(taskInput);
@@ -43,27 +43,51 @@ export function listTasks({ limit = 20, status = null } = {}) {
43
43
 
44
44
  /**
45
45
  * On startup, find tasks stuck in "running" state and reset to "pending".
46
+ * Returns the count of tasks reset so the caller can log accordingly.
46
47
  */
47
48
  export function recoverStaleTasks() {
48
- const files = readdirSync(TASKS_DIR).filter((f) => f.endsWith(".json"));
49
49
  let recovered = 0;
50
-
51
- for (const f of files) {
52
- try {
53
- const task = JSON.parse(readFileSync(`${TASKS_DIR}/${f}`, "utf-8"));
54
- if (task.status === "running") {
55
- task.status = "pending";
56
- task.startedAt = null;
57
- writeFileSync(`${TASKS_DIR}/${f}`, JSON.stringify(task, null, 2));
58
- recovered++;
59
- console.log(`[TaskStore] Recovered stale task: ${task.id}`);
50
+ try {
51
+ const files = readdirSync(TASKS_DIR).filter((f) => f.endsWith(".json"));
52
+ for (const f of files) {
53
+ try {
54
+ const task = JSON.parse(readFileSync(`${TASKS_DIR}/${f}`, "utf-8"));
55
+ if (task.status === "running") {
56
+ task.status = "pending";
57
+ task.startedAt = null;
58
+ writeFileSync(`${TASKS_DIR}/${f}`, JSON.stringify(task, null, 2));
59
+ recovered++;
60
+ console.log(`[TaskStore] Recovered stale task: ${task.id}`);
61
+ }
62
+ } catch {
63
+ // skip corrupt files
60
64
  }
61
- } catch {
62
- // skip corrupt files
63
65
  }
66
+ } catch {
67
+ // TASKS_DIR may not exist yet on first run
64
68
  }
69
+ return recovered;
70
+ }
65
71
 
66
- if (recovered > 0) {
67
- console.log(`[TaskStore] Recovered ${recovered} stale task(s)`);
72
+ /**
73
+ * Load all tasks currently in "pending" state from disk, sorted oldest-first.
74
+ * Used by TaskQueue.init() to re-hydrate the in-memory queue after a restart.
75
+ */
76
+ export function loadPendingTasks() {
77
+ try {
78
+ const files = readdirSync(TASKS_DIR).filter((f) => f.endsWith(".json"));
79
+ return files
80
+ .map((f) => {
81
+ try { return JSON.parse(readFileSync(`${TASKS_DIR}/${f}`, "utf-8")); }
82
+ catch { return null; }
83
+ })
84
+ .filter((t) => t && t.status === "pending")
85
+ .sort((a, b) => {
86
+ // Sort by priority first (lower number = higher priority), then by createdAt (oldest first)
87
+ if (a.priority !== b.priority) return (a.priority || 5) - (b.priority || 5);
88
+ return new Date(a.createdAt) - new Date(b.createdAt);
89
+ });
90
+ } catch {
91
+ return [];
68
92
  }
69
93
  }
@@ -6,6 +6,7 @@ import mcpManager from "./mcp/MCPManager.js";
6
6
  import tenantContext from "./tenants/TenantContext.js";
7
7
 
8
8
  skillLoader.load();
9
+ skillLoader.embedSkills().catch(() => {}); // Pre-compute skill embeddings at startup (fire-and-forget)
9
10
 
10
11
  /**
11
12
  * Build the system prompt dynamically by composing modular sections.
@@ -48,7 +49,7 @@ function _getContextMemoryPaths() {
48
49
  /**
49
50
  * Inject the top-k most semantically relevant memories for this specific task.
50
51
  * Only runs when OPENAI_API_KEY is set and the embeddings store has entries.
51
- * Falls back silently never blocks startup or errors out.
52
+ * Falls back silently - never blocks startup or errors out.
52
53
  */
53
54
  async function renderSemanticRecall(taskInput) {
54
55
  if (!taskInput || taskInput.length < 10) return null;
@@ -101,7 +102,7 @@ You MUST respond with a JSON object matching this exact schema on every turn:
101
102
  - Set finalResponse to true
102
103
 
103
104
  ## CRITICAL RULES:
104
- 1. NEVER set finalResponse to true unless the work is VERIFIED complete not just written, but confirmed working.
105
+ 1. NEVER set finalResponse to true unless the work is VERIFIED complete - not just written, but confirmed working.
105
106
  2. If the user asks you to DO something (fix, create, edit, build, search, etc.), your FIRST response MUST be type "tool_call". Not text. Not a plan. A tool call.
106
107
  3. Chain multiple tool calls across turns. After each tool result, decide: need more tools? Call another. Done with verification? Set finalResponse true.
107
108
  4. If a tool fails, try an alternative approach. Do NOT give up and ask the user to do it manually.
@@ -245,15 +246,15 @@ Append to today's daily log. Use to track task progress and decisions.
245
246
  ### spawnAgent(taskDescription, optionsJson?)
246
247
  Spawn a sub-agent for a single task.
247
248
  - optionsJson: {"profile":"coder","extraTools":["sendEmail"],"parentContext":"spec string","model":"openai:gpt-4.1-mini"}
248
- - profile: "researcher" | "coder" | "writer" | "analyst" focused tool set for the task type
249
+ - profile: "researcher" | "coder" | "writer" | "analyst" - focused tool set for the task type
249
250
  - extraTools: add specific tools on top of the profile (e.g. researcher that also needs writeFile)
250
- - tools: explicit tool list overrides profile entirely when you need exact control
251
- - taskDescription must be comprehensive sub-agent has no other context
251
+ - tools: explicit tool list - overrides profile entirely when you need exact control
252
+ - taskDescription must be comprehensive - sub-agent has no other context
252
253
 
253
254
  ### parallelAgents(tasksJson, sharedOptionsJson?)
254
255
  Spawn multiple sub-agents in parallel. Each task can have its own profile.
255
- - tasksJson: array of {"description":"...","options":{"profile":"coder"}} each must be self-contained
256
- - sharedOptionsJson: {"sharedContext":"..."} spec/contract shared with ALL agents before their task
256
+ - tasksJson: array of {"description":"...","options":{"profile":"coder"}} - each must be self-contained
257
+ - sharedOptionsJson: {"sharedContext":"..."} - spec/contract shared with ALL agents before their task
257
258
  - Always pass workspace path in sharedContext when agents need to share artifacts via filesystem
258
259
 
259
260
  ### manageAgents(action, paramsJson?)
@@ -261,14 +262,14 @@ List, kill, or steer running sub-agents. action: "list"|"kill"|"steer". paramsJs
261
262
 
262
263
  ### useMCP(serverName, taskDescription)
263
264
  Delegate a task to a specialist agent for the named MCP server.
264
- - serverName: the MCP server to use check "Connected MCP Servers" section for available servers
265
- - taskDescription: full task spec the specialist has zero other context, include all details
265
+ - serverName: the MCP server to use - check "Connected MCP Servers" section for available servers
266
+ - taskDescription: full task spec - the specialist has zero other context, include all details
266
267
  - The specialist gets ONLY that server's tools: lean context, no noise from built-in tools
267
268
 
268
269
  ### manageMCP(action, paramsJson?)
269
270
  Inspect connected MCP servers and their available tools at runtime.
270
- - list / status: no params all servers with connection status and tool names
271
- - tools: paramsJson {"server":"github"} full tool list for one server, or {} for all
271
+ - list / status: no params - all servers with connection status and tool names
272
+ - tools: paramsJson {"server":"github"} - full tool list for one server, or {} for all
272
273
 
273
274
  ### delegateToAgent(agentUrl, taskInput)
274
275
  Delegate to an external AI agent via A2A protocol.
@@ -276,11 +277,11 @@ Delegate to an external AI agent via A2A protocol.
276
277
  ## Project Tracking
277
278
 
278
279
  ### projectTracker(action, paramsJson?)
279
- Track multi-step project progress. Persisted to disk survives crashes and timeouts.
280
+ Track multi-step project progress. Persisted to disk - survives crashes and timeouts.
280
281
  - createProject: paramsJson {"name":"...","description":"...","tasks":["step 1","step 2",...]}
281
282
  - addTask: paramsJson {"projectId":"...","title":"...","description":"..."}
282
283
  - updateTask: paramsJson {"projectId":"...","taskId":"t1","status":"in_progress|done|failed|skipped","notes":"..."}
283
- - getProject: paramsJson {"projectId":"..."} shows ✅⬜🔄❌ status per task
284
+ - getProject: paramsJson {"projectId":"..."} - shows ✅⬜🔄❌ status per task
284
285
  - listProjects: paramsJson {} or {"status":"in_progress|done"}
285
286
  - deleteProject: paramsJson {"projectId":"..."}
286
287
 
@@ -296,7 +297,7 @@ function renderMCPTools() {
296
297
 
297
298
  const serverList = servers
298
299
  .map((s) => {
299
- const desc = s.description ? ` ${s.description}` : "";
300
+ const desc = s.description ? ` - ${s.description}` : "";
300
301
  return `- **${s.name}**${desc} (${s.toolCount} tools: ${s.toolNames.join(", ")})`;
301
302
  })
302
303
  .join("\n");
@@ -308,7 +309,7 @@ The following MCP servers are connected. Use \`useMCP(serverName, taskDescriptio
308
309
  ${serverList}
309
310
 
310
311
  Use \`manageMCP("list")\` to check server connection status at any time.
311
- Do NOT call mcp__ tools directly always route through \`useMCP\`. The specialist agent receives only that server's tools for focused, efficient execution.`;
312
+ Do NOT call mcp__ tools directly - always route through \`useMCP\`. The specialist agent receives only that server's tools for focused, efficient execution.`;
312
313
  }
313
314
 
314
315
  function renderToolUsageRules() {
@@ -322,7 +323,7 @@ function renderToolUsageRules() {
322
323
  ## Choose the Right Tool
323
324
  - **Small, targeted change** (fix a line, rename a variable, replace a block): use editFile
324
325
  - **Major rewrite or adding lots of content** (new CSS sections, restructuring): use writeFile to rewrite the entire file
325
- - **editFile keeps failing?** Switch to writeFile read the full file, modify the content, write it all back
326
+ - **editFile keeps failing?** Switch to writeFile - read the full file, modify the content, write it all back
326
327
  - **Need to find something?** Use searchContent before reading multiple files
327
328
  - **Need file list?** Use listDirectory or searchFiles, not executeCommand("ls")
328
329
 
@@ -330,7 +331,7 @@ function renderToolUsageRules() {
330
331
  - If editFile fails because oldString wasn't found: re-read the file to get the exact current content, then retry
331
332
  - If editFile fails because params are wrong: remember it needs EXACTLY 3 string params (filePath, oldString, newString)
332
333
  - If a command fails: read the error, diagnose, try a different approach
333
- - NEVER tell the user to do something manually. You have tools use them.
334
+ - NEVER tell the user to do something manually. You have tools - use them.
334
335
 
335
336
  ## Don't Over-Engineer
336
337
  - Only make changes that are directly requested or clearly necessary.
@@ -338,7 +339,7 @@ function renderToolUsageRules() {
338
339
  - Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
339
340
  - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs).
340
341
  - Don't use feature flags or backwards-compatibility shims when you can just change the code.
341
- - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed three similar lines of code is better than a premature abstraction.
342
+ - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed - three similar lines of code is better than a premature abstraction.
342
343
  - Avoid backwards-compatibility hacks like renaming unused _vars, re-exporting types, adding "// removed" comments for removed code. If something is unused, delete it completely.
343
344
  - Do not create new files unless absolutely necessary. Prefer editing an existing file over creating a new one.
344
345
 
@@ -350,7 +351,7 @@ function renderToolUsageRules() {
350
351
 
351
352
  ## Quality Standards
352
353
  - Follow existing code conventions (naming, formatting, indentation, style).
353
- - Match the existing project's patterns check surrounding code first.
354
+ - Match the existing project's patterns - check surrounding code first.
354
355
  - Never assume a library is available without checking package.json or imports.
355
356
  - Prefer the simplest correct solution. Complexity is a cost, not a feature.
356
357
 
@@ -361,21 +362,21 @@ function renderToolUsageRules() {
361
362
  - Heavy task (3+ files, multi-agent, research then build, unclear scope): plan first using projectTracker, then execute.
362
363
 
363
364
  ### Planning workflow for heavy tasks
364
- 1. Call projectTracker("createProject") breaks work into tasks AND creates a shared workspace directory.
365
- 2. The workspace path is returned pass it in sharedContext so all sub-agents know where to write artifacts.
365
+ 1. Call projectTracker("createProject") - breaks work into tasks AND creates a shared workspace directory.
366
+ 2. The workspace path is returned - pass it in sharedContext so all sub-agents know where to write artifacts.
366
367
  3. Define the shared contract (API schema, DOM structure, naming conventions) before spawning agents.
367
368
  4. Mark each task in_progress before starting, done with notes when finished.
368
369
  5. If interrupted, call projectTracker("listProjects") to find and resume.
369
370
 
370
- ### Parallel vs sequential the decision rule
371
+ ### Parallel vs sequential - the decision rule
371
372
  - Ask: does task B need output from task A? Yes → sequential. No → parallel.
372
373
  - Never run agents in parallel when they have data dependencies. Define contracts upfront and make them independent.
373
374
  - Parallel agents communicate through the shared workspace (files), NOT through messages or return values.
374
375
 
375
376
  ### Choosing a profile for sub-agents
376
- - researcher: gather info, browse web, write findings no shell execution
377
- - coder: read/write/run full loop for building, fixing, testing
378
- - writer: produce documents and reports no shell, no browser
377
+ - researcher: gather info, browse web, write findings - no shell execution
378
+ - coder: read/write/run full loop - for building, fixing, testing
379
+ - writer: produce documents and reports - no shell, no browser
379
380
  - analyst: data processing with shell scripts + web + vision
380
381
  - No profile: gets the default 27-tool set (safe general-purpose)
381
382
  - Add extraTools when a profile is almost right but needs one more tool
@@ -385,8 +386,8 @@ When projectTracker creates a project, it returns a workspace path (data/workspa
385
386
  Include this in sharedContext for ALL parallel agents on that project:
386
387
  - Sub-agents write output files to workspace/ (code, reports, schemas, notes)
387
388
  - Parent reads from workspace/ to build context for the next phase
388
- - Artifacts survive crashes work is never lost
389
- - Do NOT pass full file contents back as return values write to workspace and return a summary
389
+ - Artifacts survive crashes - work is never lost
390
+ - Do NOT pass full file contents back as return values - write to workspace and return a summary
390
391
 
391
392
  ### Structured return convention
392
393
  End every sub-agent response with a structured summary block so the parent can parse results:
@@ -400,7 +401,7 @@ A sub-agent has NO context except what you give it. Write as if handing off to a
400
401
 
401
402
  A comprehensive task description includes:
402
403
  - Exact file path(s) to create or modify
403
- - The full spec, schema, or contract to follow (do not summarize paste the actual names, endpoints, fields)
404
+ - The full spec, schema, or contract to follow (do not summarize - paste the actual names, endpoints, fields)
404
405
  - Expected behavior and output, not just the file name
405
406
  - Any constraints (no external libraries, match existing patterns, specific format)
406
407
 
@@ -409,11 +410,12 @@ Good: "Create /project/style.css. Style these DOM elements from the shared spec:
409
410
 
410
411
  ### Sequential vs parallel agents
411
412
  - Sequential: use spawnAgent multiple times when each step needs the previous step's output (research → write → test).
412
- - Parallel: use parallelAgents when steps can run simultaneously always provide sharedContext so agents share the same contract.`;
413
+ - Parallel: use parallelAgents when steps can run simultaneously - always provide sharedContext so agents share the same contract.`;
413
414
  }
414
415
 
415
- function renderSkills(taskInput) {
416
- const skillPrompts = taskInput ? skillLoader.getSkillPrompts(taskInput) : "";
416
+ async function renderSkills(taskInput) {
417
+ if (!taskInput) return "";
418
+ const skillPrompts = await skillLoader.getSkillPromptsAsync(taskInput);
417
419
  if (!skillPrompts) return "";
418
420
  return `# Active Skills\n\n${skillPrompts}`;
419
421
  }
@@ -449,17 +451,17 @@ function renderOperationalGuidelines() {
449
451
  - Report what you DID in past tense: "Updated styles.css with hover effects" not "I will update the styles".
450
452
  - Don't narrate your tool calls. Just call the tool.
451
453
  - Don't explain what you're about to do. Just do it.
452
- - Don't ask "shall I proceed?" or "would you like me to...?" just do the work.
454
+ - Don't ask "shall I proceed?" or "would you like me to...?" - just do the work.
453
455
  - Only ask for confirmation before DELETING files or running destructive commands.
454
456
 
455
457
  ## Understanding Requirements
456
- - Read between the lines. Vague requests have implied intent infer it.
458
+ - Read between the lines. Vague requests have implied intent - infer it.
457
459
  - "make it look better" → proper spacing, typography, color contrast, responsive layout
458
460
  - "fix the bug" → find root cause, fix it properly, verify it's gone
459
461
  - "add more features" → add meaningful features that fit the context of the app
460
462
  - If truly ambiguous (two valid interpretations with different outcomes), ask ONE focused question. Otherwise just do the most sensible thing.
461
463
  - Don't take requirements hyper-literally. "Fix the login" means fix the whole login flow, not just the one line mentioned.
462
- - Match the existing code style, patterns, and conventions check surrounding code first.
464
+ - Match the existing code style, patterns, and conventions - check surrounding code first.
463
465
 
464
466
  ## Workflow: Read → Understand → Act → Verify → Fix → Report
465
467
  1. **Read:** Read every file you will touch BEFORE touching it. Never edit blind.
@@ -501,26 +503,26 @@ When you build or modify any UI (web app, landing page, dashboard, component):
501
503
 
502
504
  ## When Blocked
503
505
  - If your approach is blocked, do NOT brute force. Read the error, understand the root cause, try a different approach.
504
- - If a tool fails twice with the same params, stop and diagnose don't retry the same thing.
506
+ - If a tool fails twice with the same params, stop and diagnose - don't retry the same thing.
505
507
  - If editFile keeps failing to match, re-read the file to get the exact current content, then retry.
506
- - Never use destructive workarounds (deleting files, force-pushing, wiping state) to clear a blocker investigate first.
508
+ - Never use destructive workarounds (deleting files, force-pushing, wiping state) to clear a blocker - investigate first.
507
509
 
508
510
  ## What NOT To Do
509
511
  - NEVER claim you "fixed" or "updated" something without actually calling writeFile or editFile
510
- - NEVER describe a plan and stop execute the plan using tools
512
+ - NEVER describe a plan and stop - execute the plan using tools
511
513
  - NEVER ask the user to copy-paste code or make changes manually
512
- - NEVER ask the user to run tests, start a server, or open a browser do it yourself
513
- - NEVER tell the user "you should..." or "you can..." you do it
514
- - NEVER give up after one failed attempt try alternative approaches
514
+ - NEVER ask the user to run tests, start a server, or open a browser - do it yourself
515
+ - NEVER tell the user "you should..." or "you can..." - you do it
516
+ - NEVER give up after one failed attempt - try alternative approaches
515
517
  - NEVER set finalResponse to true without having verified the result
516
- - NEVER output text explaining what you will do next between tool calls just call the next tool
518
+ - NEVER output text explaining what you will do next between tool calls - just call the next tool
517
519
  - NEVER over-engineer. Only make changes that are directly requested or clearly necessary.
518
520
  - NEVER add features, refactor, or "improve" code beyond what was asked.
519
521
  - NEVER add comments, docstrings, or type annotations to code you didn't touch.
520
- - NEVER introduce security vulnerabilities if you spot one you created, fix it immediately.
522
+ - NEVER introduce security vulnerabilities - if you spot one you created, fix it immediately.
521
523
  - NEVER ask a question you can answer yourself with a tool call.`;
522
524
  }
523
525
 
524
526
  // Note: buildSystemPrompt is now async. Use `await buildSystemPrompt(taskInput)` at call sites.
525
527
  // This legacy sync export is kept for any import that doesn't need task-specific recall.
526
- export const systemPrompt = { role: "system", content: "" }; // placeholder rebuilt per-task
528
+ export const systemPrompt = { role: "system", content: "" }; // placeholder - rebuilt per-task