daemora 1.0.8 → 1.0.10

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.
@@ -52,6 +52,7 @@ export async function buildSystemPrompt(taskInput, promptMode = "full", runtimeM
52
52
  renderMCPTools(),
53
53
  renderToolUsageRules(),
54
54
  renderSkills(taskInput, 10),
55
+ renderMemory(),
55
56
  renderSubagentContext(runtimeMeta.taskDescription || taskInput),
56
57
  ])
57
58
  : await Promise.all([
@@ -65,7 +66,6 @@ export async function buildSystemPrompt(taskInput, promptMode = "full", runtimeM
65
66
  renderMemory(),
66
67
  renderSemanticRecall(taskInput),
67
68
  renderDailyLog(),
68
- renderOperationalGuidelines(),
69
69
  ]);
70
70
 
71
71
  const runtime = renderRuntime(runtimeMeta);
@@ -149,30 +149,47 @@ You MUST respond with a JSON object matching this exact schema on every turn:
149
149
  }
150
150
  \`\`\`
151
151
 
152
- ## Rules for each response type:
153
-
154
- ### When you need to use a tool (type = "tool_call"):
155
- - Set type to "tool_call"
156
- - Set tool_call.tool_name to the tool name
157
- - Set tool_call.params to an array of STRING arguments (even numbers must be strings)
158
- - Set text_content to null
159
- - Set finalResponse to false
160
- - You will receive the tool result in the next message, then continue
161
-
162
- ### When you are truly finished (type = "text"):
163
- - Set type to "text"
164
- - Set text_content to a brief summary of what you DID (past tense)
165
- - Set tool_call to null
166
- - Set finalResponse to true
167
-
168
- ## CRITICAL RULES:
169
- 1. NEVER set finalResponse to true unless the work is VERIFIED complete - not just written, but confirmed working.
170
- 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.
171
- 3. Chain multiple tool calls across turns. After each tool result, decide: need more tools? Call another. Done with verification? Set finalResponse true.
172
- 4. If a tool fails, try an alternative approach. Do NOT give up and ask the user to do it manually.
173
- 5. After writing or editing any file, ALWAYS read it back to verify the content is correct before moving on.
174
- 6. After any coding task, run the build/test command. If it fails, fix the errors and run again. Repeat until it passes. NEVER set finalResponse true while a build is still failing.
175
- 7. NEVER claim you "fixed" or "created" something without having called writeFile or editFile. Saying it is not doing it.`;
152
+ ## When to use each type
153
+
154
+ ### type = "tool_call"
155
+ - User asks to DO something → FIRST response is always a tool call. Not text. Not a plan.
156
+ - Set tool_call.tool_name and tool_call.params (array of STRINGS).
157
+ - Set text_content to null, finalResponse to false.
158
+ - Chain tool calls across turns until the work is verified complete.
159
+
160
+ ### type = "text"
161
+ - Conversation (greetings, questions, chat) → reply naturally. finalResponse = true.
162
+ - Task complete and verified concise outcome in 1-3 sentences. finalResponse = true.
163
+
164
+ ## Task execution rules
165
+ 1. Action requests start with a tool call immediately.
166
+ 2. Chain multiple tool calls. After each result: need more? Call another. Done? Verify first, then finalize.
167
+ 3. After writing/editing any file, read it back to verify.
168
+ 4. After code changes, run build/tests. Fix failures until clean.
169
+ 5. Tool fails try a different approach. That fails try another. Exhaust every option before reporting failure.
170
+ 6. Never give up. Never ask the user to do it manually. Never report a problem without attempting to solve it.
171
+ 7. Never claim you did something without actually calling the tool.
172
+ 8. Never set finalResponse=true while errors or failures exist.
173
+
174
+ ## Understanding user intent
175
+ - Read the full request carefully. Identify exactly what the user wants done.
176
+ - Infer context from conversation history, memory, and available information.
177
+ - If the request has multiple parts, handle all of them. Don't skip any.
178
+ - If genuinely ambiguous, ask ONE focused question. Otherwise just do it.
179
+
180
+ ## Final response format
181
+ - 1-3 sentences. What happened, from the user's perspective.
182
+ - Never dump tool output, full email bodies, API responses, status codes, message IDs, or JSON.
183
+ - Never ask what to do next or offer follow-up options.
184
+ - Never expose internal details (tool names, IDs, technical artifacts).
185
+
186
+ ## Output efficiency
187
+ These rules apply to text responses sent to the user — NOT to tool params, sub-agent instructions, or task descriptions (those must remain detailed and complete).
188
+ - Go straight to the point. Try the simplest approach first.
189
+ - Lead with the answer or action, not the reasoning.
190
+ - Skip filler words, preamble, and unnecessary transitions.
191
+ - If you can say it in one sentence, don't use three.
192
+ - Focus text on: decisions needing input, status updates at milestones, errors that change the plan.`;
176
193
  }
177
194
 
178
195
  function renderToolDocs() {
@@ -188,11 +205,19 @@ ${unconfigured.map(t => `- ${t} — needs: ${TOOL_REQUIRED_KEYS[t].join(" or ")}
188
205
  return `# Available Tools
189
206
 
190
207
  All tool params are STRINGS. Pass them as an array of strings.
208
+ Use existing conversation context first — if you already have the data from a previous tool call, web search, file read, or user message, work with that. Only call a tool again when you need fresh or missing information.
191
209
 
192
210
  ## File Operations
193
- - readFile(filePath, offset?, limit?) Read file with line numbers. Always read before editing.
211
+ Always use absolute paths. Resolve ~ and relative paths from the user's context before calling any file tool.
212
+ - MUST read a file before modifying it. Never edit blind — this will error if you haven't read the file first.
213
+ - Don't re-read files already in context. Use existing content — only re-read if you need fresh state after an edit.
214
+ - Read only what you need: use offset/limit to target specific sections, not the entire file.
215
+ - Prefer editFile for modifying existing files — it only sends the diff. Most edits should use this.
216
+ - applyPatch for multi-hunk changes — better than multiple editFile calls.
217
+ - writeFile only for creating new files or complete rewrites. Never writeFile to change a few lines.
218
+ - readFile(filePath, offset?, limit?) — Read file with line numbers. Use offset/limit to read specific sections.
194
219
  - writeFile(filePath, content) — Create or overwrite file. Content is the complete file.
195
- - editFile(filePath, oldString, newString) — Find-and-replace (exactly 3 params). Read file first to get exact match string.
220
+ - editFile(filePath, oldString, newString) — Find-and-replace (exactly 3 params). Supports flexible whitespace matching.
196
221
  - applyPatch(filePath, patch) — Apply unified diff patch. Better than editFile for multi-hunk changes.
197
222
  - listDirectory(dirPath) — List files and folders with types and sizes.
198
223
  - searchFiles(pattern, directory?, optionsJson?) — Find files by name pattern. opts: {"sortBy":"modified","maxDepth":3}
@@ -235,6 +260,7 @@ ${_isToolConfigured("textToSpeech") ? `- textToSpeech(text, optionsJson?) — Te
235
260
  - sendFile(channel, target, filePath, caption?) — Send file to a DIFFERENT user on a specific channel.
236
261
 
237
262
  ## Memory
263
+ Persistent memory per tenant. Contents survive across conversations. Consult memory to build on previous experience.
238
264
  - readMemory() — Read long-term MEMORY.md.
239
265
  - writeMemory(entry, category?) — Add timestamped entry. category: "user-prefs", "project", "learned", etc.
240
266
  - searchMemory(query, optionsJson?) — Search MEMORY.md and daily logs. opts: {"category":"...","limit":50}
@@ -243,9 +269,28 @@ ${_isToolConfigured("textToSpeech") ? `- textToSpeech(text, optionsJson?) — Te
243
269
  - readDailyLog(date?) — Read daily log for date (YYYY-MM-DD). Omit for today.
244
270
  - writeDailyLog(entry) — Append to today's daily log.
245
271
 
272
+ ### What to save
273
+ - User preferences for workflow, tools, and communication style.
274
+ - Key architectural decisions, important file paths, and project structure.
275
+ - Solutions to recurring problems and debugging insights.
276
+ - When the user asks to remember something across sessions, save it immediately.
277
+
278
+ ### What NOT to save
279
+ - Session-specific context (current task details, in-progress work, temporary state).
280
+ - Speculative or unverified conclusions from a single interaction.
281
+ - Information that duplicates what's already in memory — check first, update existing entries.
282
+
283
+ ### When to use memory
284
+ - Start of a new conversation → readMemory() to recall user preferences and context.
285
+ - User gives a preference or rule → writeMemory() immediately, don't wait.
286
+ - User asks to forget something → find and remove the relevant entry.
287
+ - Learned something stable across multiple interactions → save it.
288
+ - Daily log for task tracking → writeDailyLog() at end of significant work.
289
+
246
290
  ## Agents
291
+ For complex multi-agent tasks, load \`readFile("skills/orchestration.md")\` first — covers parallel execution, contract-based planning, workspace artifacts, and coordination patterns.
247
292
  - spawnAgent(taskDescription, optionsJson?) — Spawn sub-agent. opts: {"profile":"coder|researcher|writer|analyst","extraTools":[...],"skills":["skills/coding.md"],"parentContext":"...","model":"..."}. Pass skills array with skill paths from the Available Skills list — the skill content is injected directly into the sub-agent so it can follow the instructions without loading them. Task description must be comprehensive — sub-agent has no other context.
248
- - parallelAgents(tasksJson, sharedOptionsJson?) — Spawn multiple agents in parallel. tasksJson: [{"description":"...","options":{...}}]. sharedOptionsJson: {"sharedContext":"..."}. Always pass workspace path in sharedContext.
293
+ - parallelAgents(tasksJson, sharedOptionsJson?) — Spawn multiple agents in parallel. tasksJson: [{"description":"...","options":{...}}]. sharedOptionsJson: {"sharedContext":"..."}. Always pass workspace path and shared contract in sharedContext.
249
294
  - manageAgents(action, paramsJson?) — List, kill, or steer agents. action: "list"|"kill"|"steer".
250
295
 
251
296
  ### useMCP(serverName, taskDescription)
@@ -285,45 +330,50 @@ The following MCP servers are connected. Use \`useMCP(serverName, taskDescriptio
285
330
 
286
331
  ${serverList}
287
332
 
288
- **IMPORTANT: ALWAYS prefer MCP server tools over built-in equivalents.** For example:
289
- - To send email → use \`useMCP("Fastn", ...)\` (gmail_send_mail) instead of \`sendEmail\`
290
- - To manage calendar → use \`useMCP("Fastn", ...)\` instead of built-in tools
291
- - If an MCP server provides a capability, ALWAYS use it via \`useMCP\` first. Only fall back to built-in tools if no MCP server offers that capability.
333
+ **Prefer MCP servers over built-in tools** when both can do the job. Route tasks through \`useMCP(serverName, taskDescription)\` — the specialist gets only that server's tools. Do not call mcp__ tools directly.
292
334
 
293
- Do NOT call mcp__ tools directly - always route through \`useMCP\`. The specialist agent receives only that server's tools for focused, efficient execution.
294
- Use \`manageMCP("list")\` to check server connection status at any time.`;
335
+ **Never expose MCP tool names to the user.** When describing capabilities, use natural language (e.g. "I can manage your calendar" not "I have google_calendar_create_event"). Internal tool names are implementation details.`;
295
336
  }
296
337
 
297
338
  function renderToolUsageRules() {
298
339
  return `# Tool Usage Rules
299
340
 
300
- ## Read Before Edit
301
- - ALWAYS read a file before modifying it. Never edit blind.
302
- - Use enough context in oldString for unambiguous match.
341
+ ## Workflow
342
+ 1. Read understand before touching anything.
343
+ 2. Act editFile for small changes, writeFile for rewrites. Use tools, never tell the user to do it manually.
344
+ 3. Verify → readFile after writes. Run build/tests after code changes.
345
+ 4. Fix → build/test fails → fix and re-verify until clean.
346
+ 5. Report → 1-3 sentences. What happened, key outcomes. No raw output, no internal details.
303
347
 
304
- ## Choose the Right Tool
305
- - Small change → editFile. Major rewrite → writeFile. editFile keeps failing → switch to writeFile.
306
- - Find content → searchContent/grep. Find files → searchFiles/glob/listDirectory (not executeCommand("ls")).
348
+ ## Tool Selection
349
+ - Small change → editFile. Full rewrite → writeFile. editFile keeps failing → switch to writeFile.
350
+ - Find content → searchContent/grep. Find files → searchFiles/glob/listDirectory.
351
+ - editFile oldString not found → re-read file, retry with exact content.
307
352
 
308
353
  ## Error Recovery
309
- - editFile oldString not found re-read file, retry with exact content.
310
- - Command failsread error, diagnose, try different approach.
311
- - NEVER tell user to do something manually. Use tools.
312
-
313
- ## Don't Over-Engineer
314
- - Only make changes directly requested or clearly necessary.
315
- - No extra features, refactoring, or "improvements" beyond what was asked.
316
- - No comments/docstrings/type annotations on untouched code.
317
- - No error handling for impossible scenarios. No premature abstractions.
318
- - Unused code → delete it completely. No backwards-compatibility hacks.
319
-
320
- ## Security
321
- - No command injection, XSS, SQL injection, path traversal. Fix insecure code immediately.
322
- - Never hardcode secrets. Use environment variables. Sanitize user input at boundaries.
323
-
324
- ## Quality
325
- - Follow existing code conventions. Match project patterns. Check surrounding code first.
326
- - Prefer simplest correct solution. Complexity is a cost.`;
354
+ - Tool fails → read error, try different approach. Fails again → try another. Exhaust options before reporting failure.
355
+ - Same params fail twice stop and diagnose. Don't brute force.
356
+ - Never use destructive workarounds to clear a blocker.
357
+
358
+ ## Code Quality
359
+ - Read before edit. Always. Use enough context in oldString for unambiguous match.
360
+ - Follow existing conventions. Match project patterns. Simplest correct solution wins.
361
+ - Only change what's requested. No extra features, refactoring, or "improvements" beyond scope.
362
+ - No comments/docstrings on untouched code. No error handling for impossible scenarios.
363
+ - Unused code → delete completely. No backwards-compatibility hacks.
364
+ - No command injection, XSS, SQL injection, path traversal. Never hardcode secrets.
365
+
366
+ ## What NOT To Do
367
+ - NEVER expose raw API responses, status codes, message IDs, or internal artifacts.
368
+ - NEVER ask what to do next or offer follow-up options. Either do it or don't.
369
+ - NEVER claim "fixed" without calling writeFile/editFile. NEVER plan without executing.
370
+ - NEVER ask user to do things manually. NEVER give up after one failure.
371
+ - NEVER set finalResponse true without verification or while errors exist.
372
+
373
+ ## Context Management
374
+ - \`<conversation-summary>\` blocks are compacted history — treat as ground truth for earlier work.
375
+ - Don't re-do work mentioned in the summary. Continue from where it left off.
376
+ - If context is growing long, write key decisions to memory before they get compacted.`;
327
377
  }
328
378
 
329
379
  async function renderSkills(taskInput, limit = 20) {
@@ -343,6 +393,7 @@ async function renderSkills(taskInput, limit = 20) {
343
393
  return `# Available Skills
344
394
 
345
395
  Before replying, scan this list. If a skill applies, use readFile to load it, then follow it.
396
+ Skills that need API keys or credentials access them from the runtime environment automatically — never ask the user for keys in chat.
346
397
 
347
398
  ${lines.join("\n")}${dirHint}`;
348
399
  }
@@ -369,38 +420,7 @@ function renderDailyLog() {
369
420
  return `# Today's Log (${today})\n\n${dailyLog}`;
370
421
  }
371
422
 
372
- function renderOperationalGuidelines() {
373
- return `# Operational Guidelines
374
-
375
- ## Tone & Style
376
- - Natural, warm, direct. Match the user's tone. Never robotic or sycophantic.
377
- - Final responses: 1-3 sentences. Report outcomes, not process.
378
- - Casual messages get casual responses — don't reach for tools on conversational input.
379
- - Never expose internal details (tool names, IDs, JSON) in final responses.
380
-
381
- ## Understanding Requirements
382
- - Infer implied intent from vague requests.
383
- - If truly ambiguous, ask ONE focused question. Otherwise just do it.
384
- - Match existing code style, patterns, and conventions.
385
-
386
- ## Workflow: Read → Act → Verify → Fix → Report
387
- 1. **Read** every file before touching it.
388
- 2. **Act** with tools. editFile for small changes, writeFile for rewrites.
389
- 3. **Verify** — readFile after writes. Run build/tests after code changes.
390
- 4. **Fix** — if build/test fails, fix and re-verify. Loop until clean.
391
- 5. **Report** — set finalResponse true only after verification. Summarize in 1-3 sentences.
392
- - NEVER set finalResponse true while a build error or test failure exists.
393
-
394
- ## When Blocked
395
- - Don't brute force. Read the error, try a different approach.
396
- - Tool fails twice with same params → stop and diagnose.
397
- - Never use destructive workarounds to clear a blocker.
398
-
399
- ## What NOT To Do
400
- - NEVER claim "fixed" without calling writeFile/editFile. NEVER plan without executing.
401
- - NEVER ask user to do things manually. NEVER give up after one failure.
402
- - NEVER set finalResponse true without verification. NEVER over-engineer.`;
403
- }
423
+ // renderOperationalGuidelines merged into renderToolUsageRules
404
424
 
405
425
  function renderSubagentContext(taskDescription) {
406
426
  if (!taskDescription) return null;
@@ -421,7 +441,10 @@ function renderRuntime(meta = {}) {
421
441
  if (meta.model) parts.push(`model=${meta.model}`);
422
442
  if (meta.thinkingLevel) parts.push(`thinking=${meta.thinkingLevel}`);
423
443
  if (meta.agentId) parts.push(`agent=${meta.agentId}`);
424
- if (parts.length === 0) return null;
444
+ parts.push(`date=${new Date().toISOString().split("T")[0]}`);
445
+ parts.push(`os=${process.platform}/${process.arch}`);
446
+ parts.push(`cwd=${process.cwd()}`);
447
+ parts.push(`shell=${process.env.SHELL || "unknown"}`);
425
448
  return `Runtime: ${parts.join(" | ")}`;
426
449
  }
427
450
 
package/src/cli.js CHANGED
@@ -550,6 +550,12 @@ async function handleMCP(action, args) {
550
550
 
551
551
  let serverConfig;
552
552
  if (commandOrUrl.startsWith("http://") || commandOrUrl.startsWith("https://")) {
553
+ // Detect URLs that were truncated by shell (& splits in zsh/bash)
554
+ if (commandOrUrl.includes("?") && !commandOrUrl.includes("&") && restArgs.some(a => a.includes("="))) {
555
+ console.error(`\n ${S.cross} URL appears truncated by the shell. Wrap it in quotes:`);
556
+ console.error(` ${S.arrow} daemora mcp add ${name} "${commandOrUrl}&${restArgs.filter(a => a.includes("=")).join("&")}"\n`);
557
+ process.exit(1);
558
+ }
553
559
  const isSSE = restArgs.includes("--sse");
554
560
  serverConfig = { url: commandOrUrl, enabled: true };
555
561
  if (isSSE) serverConfig.transport = "sse";
@@ -2217,7 +2223,7 @@ ${line}
2217
2223
  ${t.dim("$")} daemora mcp env notion NOTION_TOKEN ntn_...
2218
2224
  ${t.dim("$")} daemora mcp env stripe STRIPE_SECRET_KEY sk_live_...
2219
2225
  ${t.dim("$")} daemora mcp enable notion
2220
- ${t.dim("$")} daemora mcp add myserver https://api.example.com/mcp
2226
+ ${t.dim("$")} daemora mcp add myserver "https://api.example.com/mcp?key=123&id=456"
2221
2227
  ${t.dim("$")} daemora mcp add mysse https://api.example.com/sse --sse
2222
2228
  ${t.dim("$")} daemora mcp remove github
2223
2229
  ${t.dim("$")} daemora mcp add (interactive - prompts for everything)
@@ -333,6 +333,78 @@ export const models = {
333
333
  tier: "cheap",
334
334
  },
335
335
 
336
+ // ─── xAI ───────────────────────────────────────────────────────────────────
337
+
338
+ "xai:grok-4": {
339
+ provider: "xai", model: "grok-4",
340
+ contextWindow: 131_072, compactAt: 90_000,
341
+ costPer1kInput: 0.003, costPer1kOutput: 0.015,
342
+ capabilities: ["text", "tools", "structured-output"],
343
+ tier: "standard",
344
+ },
345
+ "xai:grok-3-beta": {
346
+ provider: "xai", model: "grok-3-beta",
347
+ contextWindow: 131_072, compactAt: 90_000,
348
+ costPer1kInput: 0.003, costPer1kOutput: 0.015,
349
+ capabilities: ["text", "tools"],
350
+ tier: "standard",
351
+ },
352
+ "xai:grok-3-mini-beta": {
353
+ provider: "xai", model: "grok-3-mini-beta",
354
+ contextWindow: 131_072, compactAt: 90_000,
355
+ costPer1kInput: 0.0005, costPer1kOutput: 0.005,
356
+ capabilities: ["text", "tools", "reasoning"],
357
+ tier: "cheap",
358
+ },
359
+
360
+ // ─── DeepSeek ──────────────────────────────────────────────────────────────
361
+
362
+ "deepseek:deepseek-chat": {
363
+ provider: "deepseek", model: "deepseek-chat",
364
+ contextWindow: 128_000, compactAt: 90_000,
365
+ costPer1kInput: 0.00027, costPer1kOutput: 0.0011,
366
+ capabilities: ["text", "tools", "structured-output"],
367
+ tier: "cheap",
368
+ },
369
+ "deepseek:deepseek-reasoner": {
370
+ provider: "deepseek", model: "deepseek-reasoner",
371
+ contextWindow: 128_000, compactAt: 90_000,
372
+ costPer1kInput: 0.00055, costPer1kOutput: 0.0022,
373
+ capabilities: ["text", "reasoning"],
374
+ tier: "cheap",
375
+ },
376
+
377
+ // ─── Mistral ───────────────────────────────────────────────────────────────
378
+
379
+ "mistral:mistral-large-latest": {
380
+ provider: "mistral", model: "mistral-large-latest",
381
+ contextWindow: 128_000, compactAt: 90_000,
382
+ costPer1kInput: 0.002, costPer1kOutput: 0.006,
383
+ capabilities: ["text", "tools", "structured-output"],
384
+ tier: "standard",
385
+ },
386
+ "mistral:mistral-medium-latest": {
387
+ provider: "mistral", model: "mistral-medium-latest",
388
+ contextWindow: 128_000, compactAt: 90_000,
389
+ costPer1kInput: 0.0004, costPer1kOutput: 0.002,
390
+ capabilities: ["text", "tools"],
391
+ tier: "cheap",
392
+ },
393
+ "mistral:codestral-latest": {
394
+ provider: "mistral", model: "codestral-latest",
395
+ contextWindow: 256_000, compactAt: 180_000,
396
+ costPer1kInput: 0.0003, costPer1kOutput: 0.0009,
397
+ capabilities: ["text", "tools"],
398
+ tier: "cheap",
399
+ },
400
+ "mistral:mistral-small-latest": {
401
+ provider: "mistral", model: "mistral-small-latest",
402
+ contextWindow: 128_000, compactAt: 90_000,
403
+ costPer1kInput: 0.0001, costPer1kOutput: 0.0003,
404
+ capabilities: ["text", "tools"],
405
+ tier: "cheap",
406
+ },
407
+
336
408
  // ─── Ollama (local — no cost) ────────────────────────────────────────────────
337
409
 
338
410
  "ollama:llama3": {
package/src/index.js CHANGED
@@ -871,14 +871,23 @@ app.get("/api/settings", (req, res) => {
871
871
  }
872
872
  }
873
873
 
874
- // Mask values for security
874
+ // Merge vault secrets (if unlocked) — vault takes priority
875
+ const vaultActive = secretVault.isUnlocked();
876
+ if (vaultActive) {
877
+ const vaultSecrets = secretVault.getAsEnv();
878
+ for (const key of Object.keys(vaultSecrets)) {
879
+ envVars[key] = vaultSecrets[key]; // vault overrides .env
880
+ }
881
+ }
882
+
883
+ // Uniform masking — never leak any characters
875
884
  const masked = {};
876
885
  for (const [key, val] of Object.entries(envVars)) {
877
886
  if (!val) { masked[key] = ""; continue; }
878
- masked[key] = val.length <= 4 ? "****" : val.slice(0, 4) + "*".repeat(Math.min(val.length - 4, 20));
887
+ masked[key] = "••••••••";
879
888
  }
880
889
 
881
- res.json({ vars: masked, available });
890
+ res.json({ vars: masked, available, vaultActive });
882
891
  });
883
892
 
884
893
  app.put("/api/settings", (req, res) => {
@@ -887,25 +896,52 @@ app.put("/api/settings", (req, res) => {
887
896
  return res.status(400).json({ error: "updates object is required" });
888
897
  }
889
898
 
890
- const envPath = join(__dirname, "..", ".env");
891
- let content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
899
+ const vaultActive = secretVault.isUnlocked();
900
+ const sensitivePattern = /KEY|TOKEN|SECRET|PASSWORD|PASSPHRASE|CREDENTIAL/i;
901
+
902
+ // Separate sensitive vs non-sensitive
903
+ const envUpdates = {};
904
+ const vaultUpdates = {};
892
905
 
893
906
  for (const [key, value] of Object.entries(updates)) {
894
- // Validate key format (alphanumeric + underscore only)
895
907
  if (!/^[A-Z][A-Z0-9_]*$/.test(key)) continue;
896
- const regex = new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=.*$`, "m");
897
- if (regex.test(content)) {
898
- content = content.replace(regex, `${key}=${value}`);
908
+ if (vaultActive && sensitivePattern.test(key)) {
909
+ vaultUpdates[key] = value;
899
910
  } else {
900
- content = content.trimEnd() + `\n${key}=${value}\n`;
911
+ envUpdates[key] = value;
901
912
  }
902
- // Also update process.env so changes take effect without restart
913
+ // Always update process.env so changes take effect immediately
903
914
  process.env[key] = value;
904
915
  }
905
916
 
906
- writeFileSync(envPath, content, "utf-8");
917
+ // Write non-sensitive (or all if vault locked) to .env
918
+ if (Object.keys(envUpdates).length > 0 || (!vaultActive && Object.keys(vaultUpdates).length === 0)) {
919
+ const allEnvUpdates = vaultActive ? envUpdates : { ...envUpdates, ...vaultUpdates };
920
+ const envPath = join(__dirname, "..", ".env");
921
+ let content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
922
+ for (const [key, value] of Object.entries(allEnvUpdates)) {
923
+ const regex = new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=.*$`, "m");
924
+ if (regex.test(content)) {
925
+ content = content.replace(regex, `${key}=${value}`);
926
+ } else {
927
+ content = content.trimEnd() + `\n${key}=${value}\n`;
928
+ }
929
+ }
930
+ writeFileSync(envPath, content, "utf-8");
931
+ }
932
+
933
+ // Write sensitive keys to vault
934
+ if (vaultActive && Object.keys(vaultUpdates).length > 0) {
935
+ for (const [key, value] of Object.entries(vaultUpdates)) {
936
+ secretVault.set(key, value);
937
+ }
938
+ }
939
+
940
+ const stored = vaultActive
941
+ ? { env: Object.keys(envUpdates), vault: Object.keys(vaultUpdates) }
942
+ : { env: Object.keys(updates).filter(k => /^[A-Z][A-Z0-9_]*$/.test(k)) };
907
943
 
908
- res.json({ message: `Updated ${Object.keys(updates).length} variable(s)`, updated: Object.keys(updates) });
944
+ res.json({ message: `Updated ${Object.keys(updates).length} variable(s)`, stored });
909
945
  });
910
946
 
911
947
  // --- User Profile endpoints ---
@@ -57,7 +57,7 @@ All MCP tool params must be passed as a single JSON string (the first and only a
57
57
  - **Never ask for clarification.** You have everything you need in the task description. Make reasonable decisions and proceed.
58
58
  - **Handle errors yourself.** If a tool call fails, read the error, adjust your approach, try again. Do not give up and report failure unless you have exhausted all approaches.
59
59
  - **Be thorough.** If the task says "update all tasks in a project", update all of them. If it says "research X", gather enough detail to be useful. Don't do a half job.
60
- - **End with a useful summary.** When done, set finalResponse true and write a clear summary: what was done, what was created/updated/found, and any important details the main agent needs.`,
60
+ - **End with a concise summary.** When done, set finalResponse true. Write 1-3 sentences: what was done and key outcomes. Never dump raw API responses, full JSON payloads, message IDs, status codes, or technical artifacts. The main agent will relay your response to the user.`,
61
61
  };
62
62
  }
63
63