@markus-global/cli 0.7.1 → 0.7.2

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.
package/dist/markus.mjs CHANGED
@@ -4216,7 +4216,7 @@ function withJitter(baseMs, factor = 0.2) {
4216
4216
  const jitter = baseMs * factor * (2 * Math.random() - 1);
4217
4217
  return Math.max(0, Math.round(baseMs + jitter));
4218
4218
  }
4219
- var TASK_GET_NOTES_DEFAULT, TASK_GET_DELIVERABLES_DEFAULT, TASK_GET_STATUS_HISTORY_DEFAULT, REQUIREMENT_LIST_DEFAULT, ENTITY_COMMENTS_DEFAULT, PROMPT_TASK_NOTES_MAX, PROMPT_TASK_NOTE_CHARS, PROMPT_DEP_NOTES_MAX, PROMPT_DEP_NOTE_CHARS, PROMPT_DEP_DESC_CHARS, PROMPT_EXECUTION_ROUNDS_MAX, PROMPT_LOG_ENTRY_CHARS, PROMPT_RECENT_ERRORS_MAX, PROMPT_HUMAN_COMMENTS_MAX, REVIEWER_NOTES_MAX, SYSTEM_MY_TASKS_MAX, SYSTEM_TEAM_TASKS_MAX, SYSTEM_TASK_DESC_CHARS, PROMPT_PROJECT_DESC_CHARS, PROMPT_REQUIREMENT_DESC_CHARS, REVIEWER_FILE_LIST_MAX, DELIVERABLE_TITLE_CHARS, REVISION_REASON_CHARS, TASK_MAX_RETRIES, MAILBOX_ITEM_MAX_RETRIES, COMPLETION_MARKER, COMPLETION_MARKER_INSTRUCTION, TASK_MAX_NO_SUBMIT_RETRIES, TASK_RETRY_DELAYS_MS, PREEMPT_REQUEUE_DELAY_MS, SYSTEM_KNOWLEDGE_CHARS, SYSTEM_SOPS_CHARS, SYSTEM_LONGTERM_MEMORY_CHARS, MEMORY_MD_SECTION_MAX_CHARS, MEMORY_MD_TOTAL_MAX_CHARS, SYSTEM_LESSON_ENTRIES_MAX, SYSTEM_BEST_PRACTICE_ENTRIES_MAX, SYSTEM_DELIVERABLES_CHARS, SYSTEM_DAILY_LOG_CHARS, SYSTEM_DAILY_LOG_DAYS, SYSTEM_USER_PROFILE_CHARS, SYSTEM_CHAT_SESSIONS_MAX, HEARTBEAT_DAILY_LOG_CHARS, SYSTEM_PROJECT_DESC_CHARS, SYSTEM_DELIVERABLE_PREVIEW_CHARS, SYSTEM_MAILBOX_MERGED_CHARS, SYSTEM_MAILBOX_ITEM_PREVIEW_CHARS, TASK_LIST_PAGE_SIZE, TASK_LIST_PAGE_MAX, ARCHIVE_COMPLETED_AFTER_DAYS, ARCHIVE_TERMINAL_AFTER_DAYS, ARCHIVE_REQUIREMENT_AFTER_DAYS, ARCHIVE_SCAN_INTERVAL_MS, CHANNEL_CONTEXT_MESSAGES, MAILBOX_QUEUED_TTL_MS, MAILBOX_PROCESSING_TIMEOUT_MS, WATCHDOG_INTERVAL_MS, WATCHDOG_DRIFT_THRESHOLD_MS, TRIAGE_BACKLOG_THRESHOLD, TRIAGE_PROMPT_MAX_ITEMS, TRIAGE_MAX_TOKENS, TRIAGE_TEMPERATURE, TRIAGE_CONTEXT_MESSAGES_MAX, TRIAGE_CONTEXT_MSG_CHARS, TRIAGE_ITEM_CONTENT_CHARS, TRIAGE_STALE_INFO_TTL_MS, TRIAGE_STALE_DROP_TYPES, TRIAGE_MAX_TOOL_ITERATIONS, TRIAGE_ALLOWED_TOOLS, DELIBERATION_ALLOWED_TOOLS, WORKING_MEMORY_MAX_ENTRIES, WORKING_MEMORY_MAX_CHARS, SUBAGENT_TASK_PREVIEW_CHARS, SUBAGENT_THINKING_PREVIEW_CHARS, SUBAGENT_RESULT_PREVIEW_CHARS, SUBAGENT_LOG_ENTRY_CHARS, SUBAGENT_ERROR_PREVIEW_CHARS, SUBAGENT_MAX_PARALLEL, SUBAGENT_MAX_LLM_RETRIES, SUBAGENT_RETRY_BASE_MS, SHELL_TIMEOUT_DEFAULT_MS, SHELL_TIMEOUT_MAX_MS, SHELL_MAX_SESSIONS_PER_AGENT, SHELL_SESSION_IDLE_TIMEOUT_MS, SHELL_SESSION_MAX_OUTPUT_BYTES, APPROVAL_WAIT_TIMEOUT_MS, HEARTBEAT_MIN_INITIAL_DELAY_MS, HEARTBEAT_STARTUP_JITTER_MS, LLM_CIRCUIT_RESET_RATE_LIMIT_MS, LLM_MAX_CONCURRENT_PER_PROVIDER, LLM_CONCURRENCY_JITTER_BASE_MS;
4219
+ var TASK_GET_NOTES_DEFAULT, TASK_GET_DELIVERABLES_DEFAULT, TASK_GET_STATUS_HISTORY_DEFAULT, REQUIREMENT_LIST_DEFAULT, ENTITY_COMMENTS_DEFAULT, PROMPT_TASK_NOTES_MAX, PROMPT_TASK_NOTE_CHARS, PROMPT_DEP_NOTES_MAX, PROMPT_DEP_NOTE_CHARS, PROMPT_DEP_DESC_CHARS, PROMPT_EXECUTION_ROUNDS_MAX, PROMPT_LOG_ENTRY_CHARS, PROMPT_RECENT_ERRORS_MAX, PROMPT_HUMAN_COMMENTS_MAX, REVIEWER_NOTES_MAX, SYSTEM_MY_TASKS_MAX, SYSTEM_TEAM_TASKS_MAX, SYSTEM_TASK_DESC_CHARS, PROMPT_PROJECT_DESC_CHARS, PROMPT_REQUIREMENT_DESC_CHARS, REVIEWER_FILE_LIST_MAX, DELIVERABLE_TITLE_CHARS, REVISION_REASON_CHARS, TASK_MAX_RETRIES, MAILBOX_ITEM_MAX_RETRIES, COMPLETION_MARKER, COMPLETION_MARKER_INSTRUCTION, TASK_MAX_NO_SUBMIT_RETRIES, TASK_RETRY_DELAYS_MS, PREEMPT_REQUEUE_DELAY_MS, SYSTEM_KNOWLEDGE_CHARS, SYSTEM_SOPS_CHARS, SYSTEM_LONGTERM_MEMORY_CHARS, MEMORY_MD_SECTION_MAX_CHARS, MEMORY_MD_TOTAL_MAX_CHARS, SYSTEM_LESSON_ENTRIES_MAX, SYSTEM_BEST_PRACTICE_ENTRIES_MAX, SYSTEM_DELIVERABLES_CHARS, SYSTEM_DAILY_LOG_CHARS, SYSTEM_DAILY_LOG_DAYS, SYSTEM_USER_PROFILE_CHARS, SYSTEM_CHAT_SESSIONS_MAX, HEARTBEAT_DAILY_LOG_CHARS, SYSTEM_PROJECT_DESC_CHARS, SYSTEM_DELIVERABLE_PREVIEW_CHARS, SYSTEM_MAILBOX_MERGED_CHARS, SYSTEM_MAILBOX_ITEM_PREVIEW_CHARS, TASK_LIST_PAGE_SIZE, TASK_LIST_PAGE_MAX, ARCHIVE_COMPLETED_AFTER_DAYS, ARCHIVE_TERMINAL_AFTER_DAYS, ARCHIVE_REQUIREMENT_AFTER_DAYS, ARCHIVE_SCAN_INTERVAL_MS, CHANNEL_CONTEXT_MESSAGES, MAILBOX_QUEUED_TTL_MS, MAILBOX_COALESCE_WINDOW_MS, MAILBOX_PROCESSING_TIMEOUT_MS, WATCHDOG_INTERVAL_MS, WATCHDOG_DRIFT_THRESHOLD_MS, TRIAGE_BACKLOG_THRESHOLD, TRIAGE_PROMPT_MAX_ITEMS, TRIAGE_MAX_TOKENS, TRIAGE_TEMPERATURE, TRIAGE_CONTEXT_MESSAGES_MAX, TRIAGE_CONTEXT_MSG_CHARS, TRIAGE_ITEM_CONTENT_CHARS, TRIAGE_STALE_INFO_TTL_MS, TRIAGE_STALE_DROP_TYPES, TRIAGE_MAX_TOOL_ITERATIONS, TRIAGE_ALLOWED_TOOLS, DELIBERATION_ALLOWED_TOOLS, WORKING_MEMORY_MAX_ENTRIES, WORKING_MEMORY_MAX_CHARS, SUBAGENT_TASK_PREVIEW_CHARS, SUBAGENT_THINKING_PREVIEW_CHARS, SUBAGENT_RESULT_PREVIEW_CHARS, SUBAGENT_LOG_ENTRY_CHARS, SUBAGENT_ERROR_PREVIEW_CHARS, SUBAGENT_MAX_PARALLEL, SUBAGENT_MAX_LLM_RETRIES, SUBAGENT_RETRY_BASE_MS, SHELL_TIMEOUT_DEFAULT_MS, SHELL_TIMEOUT_MAX_MS, SHELL_MAX_SESSIONS_PER_AGENT, SHELL_SESSION_IDLE_TIMEOUT_MS, SHELL_SESSION_MAX_OUTPUT_BYTES, APPROVAL_WAIT_TIMEOUT_MS, HEARTBEAT_MIN_INITIAL_DELAY_MS, HEARTBEAT_STARTUP_JITTER_MS, LLM_CIRCUIT_RESET_RATE_LIMIT_MS, LLM_MAX_CONCURRENT_PER_PROVIDER, LLM_CONCURRENCY_JITTER_BASE_MS;
4220
4220
  var init_limits = __esm({
4221
4221
  "../shared/dist/limits.js"() {
4222
4222
  "use strict";
@@ -4277,6 +4277,7 @@ var init_limits = __esm({
4277
4277
  ARCHIVE_SCAN_INTERVAL_MS = 6 * 60 * 60 * 1e3;
4278
4278
  CHANNEL_CONTEXT_MESSAGES = 40;
4279
4279
  MAILBOX_QUEUED_TTL_MS = 3 * 24 * 60 * 60 * 1e3;
4280
+ MAILBOX_COALESCE_WINDOW_MS = 200;
4280
4281
  MAILBOX_PROCESSING_TIMEOUT_MS = 10 * 60 * 1e3;
4281
4282
  WATCHDOG_INTERVAL_MS = 3e4;
4282
4283
  WATCHDOG_DRIFT_THRESHOLD_MS = 6e4;
@@ -4326,6 +4327,9 @@ var init_limits = __esm({
4326
4327
  // Working memory
4327
4328
  "update_working_memory",
4328
4329
  "clear_working_memory",
4330
+ // Long-term memory (allows recording observations during deliberation)
4331
+ "memory_save",
4332
+ "memory_update_longterm",
4329
4333
  // Inline action (lightweight communication)
4330
4334
  "notify_user",
4331
4335
  "task_comment",
@@ -4433,6 +4437,7 @@ __export(dist_exports, {
4433
4437
  LLM_MAX_CONCURRENT_PER_PROVIDER: () => LLM_MAX_CONCURRENT_PER_PROVIDER,
4434
4438
  Logger: () => Logger,
4435
4439
  MAILBOX_CATEGORIES: () => MAILBOX_CATEGORIES,
4440
+ MAILBOX_COALESCE_WINDOW_MS: () => MAILBOX_COALESCE_WINDOW_MS,
4436
4441
  MAILBOX_ITEM_MAX_RETRIES: () => MAILBOX_ITEM_MAX_RETRIES,
4437
4442
  MAILBOX_PROCESSING_TIMEOUT_MS: () => MAILBOX_PROCESSING_TIMEOUT_MS,
4438
4443
  MAILBOX_QUEUED_TTL_MS: () => MAILBOX_QUEUED_TTL_MS,
@@ -6700,8 +6705,9 @@ var init_context_engine = __esm({
6700
6705
  stable.push("- `recall_activity` \u2014 query your own past execution logs by task or activity type. Use when you need to review what you did previously (e.g., to answer a follow-up question).");
6701
6706
  stable.push("");
6702
6707
  stable.push("**Communicating with other agents**:");
6703
- stable.push("- `agent_send_message` \u2014 send a direct message to a peer agent. **By default this is asynchronous (fire-and-forget)**: the message enters their mailbox and you continue working without waiting. Set `wait_for_reply: true` only when you need the answer before you can proceed (rare \u2014 prefer async).");
6704
- stable.push("- A2A messaging is inherently **non-blocking**. You send a message, the recipient processes it on their own schedule, and may reply later via their own `agent_send_message`. Do NOT spin-wait or poll for responses.");
6708
+ stable.push("- `agent_send_message` \u2014 send a direct message to a peer agent. **Always asynchronous**: the message enters their mailbox and you continue working without blocking.");
6709
+ stable.push("- A2A messaging is **non-blocking by design**. You send a message, the recipient processes it on their own schedule, and may reply via their own `agent_send_message`. Do NOT spin-wait or poll.");
6710
+ stable.push("- Use `conversation_id` to correlate multi-turn exchanges. Record what you asked in working memory (`update_working_memory`) so you recognize the reply when it arrives as a new mailbox item.");
6705
6711
  stable.push("- For substantial work requests, create a `task_create` assigned to the target agent instead of asking via message.");
6706
6712
  stable.push("- Do NOT use A2A messages for routine task status notifications \u2014 the system handles those automatically.");
6707
6713
  }
@@ -7110,24 +7116,19 @@ ${ctx.mergedContent.slice(0, SYSTEM_MAILBOX_MERGED_CHARS)}`);
7110
7116
  lines.push("If nothing needs attention, respond with exactly: HEARTBEAT_OK");
7111
7117
  break;
7112
7118
  case "a2a": {
7113
- const waitForReply = extra?.a2aWaitForReply;
7114
7119
  lines.push("You are in an **agent-to-agent (A2A) conversation**. This context is for COORDINATION, not for executing work.");
7115
7120
  lines.push("");
7116
- if (waitForReply) {
7117
- lines.push("**Communication channel**: The peer agent is **waiting for your reply**. Your text output is sent **directly back** to the peer agent as the response. Humans do NOT see this conversation. To reach a human, use `notify_user`. To reach a different agent (not the one who messaged you), use `agent_send_message`.");
7118
- } else {
7119
- lines.push("**Communication channel**: The peer agent sent you a **one-way notification** and is **NOT waiting for a reply**. Your text output will NOT reach the sender. Humans do NOT see this conversation.");
7120
- lines.push("- To **reply to the sender**, use `agent_send_message` with the sender's agent ID.");
7121
- lines.push("- To reach a **human**, use `notify_user`.");
7122
- lines.push("- To reach a **different agent**, use `agent_send_message`.");
7123
- lines.push("- If no response is needed, just process the information silently (e.g., update your state, create tasks, take notes).");
7124
- lines.push("");
7125
- lines.push("**One-way notification etiquette**: The sender is NOT waiting. Only act if:");
7126
- lines.push("- The message contains a direct question or request for you");
7127
- lines.push("- The information changes your current work priorities");
7128
- lines.push("- You have critical corrections to share");
7129
- lines.push("Otherwise, absorb silently and continue your current work.");
7130
- }
7121
+ lines.push("**Communication channel**: All A2A messaging is **asynchronous**. The sender is NOT blocking for your reply. Humans do NOT see this conversation.");
7122
+ lines.push("- To **reply to the sender**, use `agent_send_message` with the sender's agent ID and the same `conversation_id` (if present in the message as `[conversation:...]`).");
7123
+ lines.push("- To reach a **human**, use `notify_user`.");
7124
+ lines.push("- To reach a **different agent**, use `agent_send_message`.");
7125
+ lines.push("- If no response is needed, just process the information silently (e.g., update your state, create tasks, take notes).");
7126
+ lines.push("");
7127
+ lines.push("**A2A etiquette**: Only act if:");
7128
+ lines.push("- The message contains a direct question or request for you");
7129
+ lines.push("- The information changes your current work priorities");
7130
+ lines.push("- You have critical corrections to share");
7131
+ lines.push("Otherwise, absorb silently and continue your current work.");
7131
7132
  lines.push("");
7132
7133
  lines.push("**Communication rules:**");
7133
7134
  lines.push("- Be concise and structured \u2014 your colleague needs actionable information");
@@ -8913,18 +8914,33 @@ var init_tool_selector = __esm({
8913
8914
  });
8914
8915
  pushUnique({
8915
8916
  name: "complete_deliberation",
8916
- description: "Finalize your mailbox deliberation. Declare which item to process next, which to defer/drop, and which you handled inline. Only available in deliberation mode.",
8917
+ description: "Finalize your mailbox deliberation. Declare which item(s) to process next, which to defer/drop, and which you handled inline. Supports batch processing: pass multiple IDs in process_item_ids to handle related items together in one session. Only available in deliberation mode.",
8917
8918
  inputSchema: {
8918
8919
  type: "object",
8919
8920
  properties: {
8920
- process_item_id: { type: "string", description: "ID of the mailbox item to process in full next" },
8921
+ process_item_id: { type: "string", description: "ID of the primary mailbox item to process next. Required unless process_item_ids is provided." },
8922
+ process_item_ids: { type: "array", items: { type: "string" }, description: "Batch mode: IDs of multiple items to process together in one session. Use for related items (same channel, same topic). Overrides process_item_id when length > 1." },
8923
+ batch_context: { type: "string", description: "When using batch mode, optional synthesis/instruction for how to handle the batch together." },
8921
8924
  defer_item_ids: { type: "array", items: { type: "string" }, description: "IDs of items to defer for later" },
8922
8925
  drop_item_ids: { type: "array", items: { type: "string" }, description: "IDs of stale/redundant items to drop" },
8923
8926
  inline_completed_ids: { type: "array", items: { type: "string" }, description: "IDs of items you already handled inline during this deliberation (e.g., sent a notification, posted a comment)" },
8924
8927
  reasoning: { type: "string", description: "1-2 sentence explanation of your decision" },
8925
- situational_awareness: { type: "string", description: "Your current understanding of the situation \u2014 injected into future prompts for continuity" }
8928
+ situational_awareness: { type: "string", description: "Your current understanding of the situation \u2014 injected into future prompts for continuity" },
8929
+ memory_updates: {
8930
+ type: "array",
8931
+ items: {
8932
+ type: "object",
8933
+ properties: {
8934
+ type: { type: "string", enum: ["working", "longterm"], description: "working = volatile per-session memory, longterm = persisted to MEMORY.md" },
8935
+ key: { type: "string", description: "Memory key/section name" },
8936
+ content: { type: "string", description: "Content to store" }
8937
+ },
8938
+ required: ["type", "key", "content"]
8939
+ },
8940
+ description: "Memory updates to apply after deliberation. Use to record observations, team decisions, or context for future cycles."
8941
+ }
8926
8942
  },
8927
- required: ["process_item_id", "reasoning"]
8943
+ required: ["reasoning"]
8928
8944
  }
8929
8945
  });
8930
8946
  pushUnique({
@@ -41899,14 +41915,14 @@ var require_turndown_cjs = __commonJS({
41899
41915
  } else if (node.nodeType === 1) {
41900
41916
  replacement = replacementForNode.call(self, node);
41901
41917
  }
41902
- return join31(output, replacement);
41918
+ return join32(output, replacement);
41903
41919
  }, "");
41904
41920
  }
41905
41921
  function postProcess(output) {
41906
41922
  var self = this;
41907
41923
  this.rules.forEach(function(rule) {
41908
41924
  if (typeof rule.append === "function") {
41909
- output = join31(output, rule.append(self.options));
41925
+ output = join32(output, rule.append(self.options));
41910
41926
  }
41911
41927
  });
41912
41928
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -41918,7 +41934,7 @@ var require_turndown_cjs = __commonJS({
41918
41934
  if (whitespace2.leading || whitespace2.trailing) content = content.trim();
41919
41935
  return whitespace2.leading + rule.replacement(content, node, this.options) + whitespace2.trailing;
41920
41936
  }
41921
- function join31(output, replacement) {
41937
+ function join32(output, replacement) {
41922
41938
  var s1 = trimTrailingNewlines(output);
41923
41939
  var s2 = trimLeadingNewlines(replacement);
41924
41940
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -42144,6 +42160,113 @@ async function searchSerper(query2, maxResults) {
42144
42160
  date: r.date
42145
42161
  }));
42146
42162
  }
42163
+ async function searchTavily(query2, maxResults) {
42164
+ const apiKey = process.env["TAVILY_API_KEY"];
42165
+ if (!apiKey)
42166
+ throw new Error("TAVILY_API_KEY not configured");
42167
+ let res;
42168
+ try {
42169
+ res = await proxyFetch("https://api.tavily.com/search", {
42170
+ method: "POST",
42171
+ headers: { "Content-Type": "application/json" },
42172
+ body: JSON.stringify({
42173
+ api_key: apiKey,
42174
+ query: query2,
42175
+ max_results: maxResults,
42176
+ include_answer: false
42177
+ })
42178
+ });
42179
+ } catch (err) {
42180
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
42181
+ }
42182
+ if (!res.ok)
42183
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
42184
+ const data = await res.json();
42185
+ return (data.results ?? []).slice(0, maxResults).map((r) => ({
42186
+ title: r.title,
42187
+ url: r.url,
42188
+ snippet: r.content,
42189
+ date: r.published_date
42190
+ }));
42191
+ }
42192
+ async function searchBing(query2, maxResults) {
42193
+ const apiKey = process.env["BING_SEARCH_API_KEY"];
42194
+ if (!apiKey)
42195
+ throw new Error("BING_SEARCH_API_KEY not configured");
42196
+ const params = new URLSearchParams({ q: query2, count: String(maxResults), mkt: "en-US" });
42197
+ let res;
42198
+ try {
42199
+ res = await proxyFetch(`https://api.bing.microsoft.com/v7.0/search?${params}`, {
42200
+ headers: { "Ocp-Apim-Subscription-Key": apiKey }
42201
+ });
42202
+ } catch (err) {
42203
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
42204
+ }
42205
+ if (!res.ok)
42206
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
42207
+ const data = await res.json();
42208
+ return (data.webPages?.value ?? []).slice(0, maxResults).map((r) => ({
42209
+ title: r.name,
42210
+ url: r.url,
42211
+ snippet: r.snippet,
42212
+ date: r.dateLastCrawled
42213
+ }));
42214
+ }
42215
+ async function searchGoogle(query2, maxResults) {
42216
+ const apiKey = process.env["GOOGLE_SEARCH_API_KEY"];
42217
+ const cx = process.env["GOOGLE_SEARCH_CX"];
42218
+ if (!apiKey)
42219
+ throw new Error("GOOGLE_SEARCH_API_KEY not configured");
42220
+ if (!cx)
42221
+ throw new Error("GOOGLE_SEARCH_CX not configured");
42222
+ const params = new URLSearchParams({
42223
+ key: apiKey,
42224
+ cx,
42225
+ q: query2,
42226
+ num: String(Math.min(maxResults, 10))
42227
+ });
42228
+ let res;
42229
+ try {
42230
+ res = await proxyFetch(`https://www.googleapis.com/customsearch/v1?${params}`);
42231
+ } catch (err) {
42232
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
42233
+ }
42234
+ if (!res.ok)
42235
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
42236
+ const data = await res.json();
42237
+ return (data.items ?? []).slice(0, maxResults).map((r) => ({
42238
+ title: r.title,
42239
+ url: r.link,
42240
+ snippet: r.snippet,
42241
+ date: r.pagemap?.metatags?.[0]?.["article:published_time"]
42242
+ }));
42243
+ }
42244
+ async function searchSerpApi(query2, maxResults) {
42245
+ const apiKey = process.env["SERPAPI_API_KEY"];
42246
+ if (!apiKey)
42247
+ throw new Error("SERPAPI_API_KEY not configured");
42248
+ const params = new URLSearchParams({
42249
+ api_key: apiKey,
42250
+ q: query2,
42251
+ engine: "google",
42252
+ num: String(maxResults)
42253
+ });
42254
+ let res;
42255
+ try {
42256
+ res = await proxyFetch(`https://serpapi.com/search.json?${params}`);
42257
+ } catch (err) {
42258
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
42259
+ }
42260
+ if (!res.ok)
42261
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
42262
+ const data = await res.json();
42263
+ return (data.organic_results ?? []).slice(0, maxResults).map((r) => ({
42264
+ title: r.title,
42265
+ url: r.link,
42266
+ snippet: r.snippet,
42267
+ date: r.date
42268
+ }));
42269
+ }
42147
42270
  async function searchBrave(query2, maxResults) {
42148
42271
  const apiKey = process.env["BRAVE_SEARCH_API_KEY"];
42149
42272
  if (!apiKey)
@@ -42171,6 +42294,38 @@ async function searchBrave(query2, maxResults) {
42171
42294
  date: r.page_age
42172
42295
  }));
42173
42296
  }
42297
+ async function searchExa(query2, maxResults) {
42298
+ const apiKey = process.env["EXA_API_KEY"];
42299
+ if (!apiKey)
42300
+ throw new Error("EXA_API_KEY not configured");
42301
+ let res;
42302
+ try {
42303
+ res = await proxyFetch("https://api.exa.ai/search", {
42304
+ method: "POST",
42305
+ headers: {
42306
+ "x-api-key": apiKey,
42307
+ "Content-Type": "application/json"
42308
+ },
42309
+ body: JSON.stringify({
42310
+ query: query2,
42311
+ numResults: maxResults,
42312
+ type: "auto",
42313
+ contents: { text: { maxCharacters: 300 } }
42314
+ })
42315
+ });
42316
+ } catch (err) {
42317
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
42318
+ }
42319
+ if (!res.ok)
42320
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
42321
+ const data = await res.json();
42322
+ return (data.results ?? []).slice(0, maxResults).map((r) => ({
42323
+ title: r.title,
42324
+ url: r.url,
42325
+ snippet: r.text ?? "",
42326
+ date: r.publishedDate
42327
+ }));
42328
+ }
42174
42329
  async function searchBocha(query2, maxResults) {
42175
42330
  const apiKey = process.env["BOCHA_API_KEY"];
42176
42331
  if (!apiKey)
@@ -42314,7 +42469,12 @@ var init_web_search = __esm({
42314
42469
  const maxResults = args["maxResults"] ?? 8;
42315
42470
  const backends = [
42316
42471
  { name: "Serper", fn: searchSerper },
42472
+ { name: "Tavily", fn: searchTavily },
42473
+ { name: "Bing", fn: searchBing },
42474
+ { name: "Google", fn: searchGoogle },
42475
+ { name: "SerpAPI", fn: searchSerpApi },
42317
42476
  { name: "Brave", fn: searchBrave },
42477
+ { name: "Exa", fn: searchExa },
42318
42478
  { name: "Bocha", fn: searchBocha },
42319
42479
  { name: "DuckDuckGo", fn: searchDuckDuckGo }
42320
42480
  ];
@@ -43688,6 +43848,7 @@ var init_mailbox2 = __esm({
43688
43848
  }
43689
43849
  removed += this.mergeByEntity(_AgentMailbox.TASK_COMMENT_DEDUP_TYPES, (item) => item.payload.taskId ?? item.metadata?.taskId);
43690
43850
  removed += this.mergeByEntity(_AgentMailbox.REQ_COMMENT_DEDUP_TYPES, (item) => item.payload.requirementId);
43851
+ removed += this.mergeByEntity(_AgentMailbox.CHANNEL_DEDUP_TYPES, (item) => item.payload.extra?.channelKey);
43691
43852
  return removed;
43692
43853
  }
43693
43854
  /**
@@ -43823,6 +43984,9 @@ ${typeLabel}${item.payload.content}`;
43823
43984
  static REQ_COMMENT_DEDUP_TYPES = /* @__PURE__ */ new Set([
43824
43985
  "requirement_comment"
43825
43986
  ]);
43987
+ static CHANNEL_DEDUP_TYPES = /* @__PURE__ */ new Set([
43988
+ "a2a_message"
43989
+ ]);
43826
43990
  enqueue(sourceType, payload, options) {
43827
43991
  const merged = this.tryMergeIntoExisting(sourceType, payload);
43828
43992
  if (merged) {
@@ -44165,14 +44329,33 @@ ${typeLabel}${item.payload.content}`;
44165
44329
  if (reqId) {
44166
44330
  existing = this.queue.find((i) => i.status === "queued" && _AgentMailbox.REQ_COMMENT_DEDUP_TYPES.has(i.sourceType) && i.payload.requirementId === reqId);
44167
44331
  }
44332
+ } else if (_AgentMailbox.CHANNEL_DEDUP_TYPES.has(sourceType)) {
44333
+ const channelKey = payload.extra?.channelKey;
44334
+ if (channelKey) {
44335
+ existing = this.queue.find((i) => i.status === "queued" && _AgentMailbox.CHANNEL_DEDUP_TYPES.has(i.sourceType) && i.payload.extra?.channelKey === channelKey);
44336
+ }
44168
44337
  }
44169
44338
  if (!existing)
44170
44339
  return void 0;
44171
- existing.payload.content += `
44340
+ if (_AgentMailbox.CHANNEL_DEDUP_TYPES.has(sourceType) && payload.extra?.channelKey) {
44341
+ const senderName = payload.extra?.senderName || "unknown";
44342
+ const newMsg = { senderId: payload.extra?.senderId, senderName, content: payload.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
44343
+ if (!existing.payload.messages) {
44344
+ const existingSender = existing.payload.extra?.senderName || "unknown";
44345
+ existing.payload.messages = [{ senderId: existing.payload.extra?.senderId, senderName: existingSender, content: existing.payload.content, timestamp: existing.queuedAt }];
44346
+ }
44347
+ existing.payload.messages.push(newMsg);
44348
+ existing.payload.content += `
44349
+
44350
+ ---
44351
+ [${senderName}]: ${payload.content}`;
44352
+ } else {
44353
+ existing.payload.content += `
44172
44354
 
44173
44355
  ---
44174
44356
 
44175
44357
  ${payload.content}`;
44358
+ }
44176
44359
  existing.payload.summary += ` (+1)`;
44177
44360
  this.persistence?.updateStatus(existing.id, "queued", existing);
44178
44361
  log13.debug("Mailbox enqueue-time dedup: merged into existing item", {
@@ -44183,12 +44366,15 @@ ${payload.content}`;
44183
44366
  return existing;
44184
44367
  }
44185
44368
  /**
44186
- * Insert item into the queue maintaining priority + FIFO order.
44369
+ * Insert item into the queue maintaining priority order.
44370
+ * Within the same priority, newer items go first (LIFO) so the most
44371
+ * recent message is processed before older ones — a user's latest
44372
+ * instruction may supersede earlier ones.
44187
44373
  */
44188
44374
  insertSorted(item) {
44189
44375
  let insertIdx = this.queue.length;
44190
44376
  for (let i = 0; i < this.queue.length; i++) {
44191
- if (this.queue[i].priority > item.priority) {
44377
+ if (this.queue[i].priority >= item.priority) {
44192
44378
  insertIdx = i;
44193
44379
  break;
44194
44380
  }
@@ -44240,6 +44426,8 @@ var init_attention = __esm({
44240
44426
  lastTriageResult;
44241
44427
  /** True while a full-session deliberation is in progress (suppresses yield points). */
44242
44428
  isDeliberating = false;
44429
+ /** Set to true when a human_chat arrives during deliberation, causing early abort. */
44430
+ deliberationAbortSignal = false;
44243
44431
  /** Tracks whether the last yield-point decision was 'cancel' vs 'preempt'. */
44244
44432
  lastYieldDecision;
44245
44433
  unsubscribeNewItem;
@@ -44340,7 +44528,7 @@ var init_attention = __esm({
44340
44528
  });
44341
44529
  this.launchLoop();
44342
44530
  }
44343
- if (this.running && !this.currentFocus && this.state === "idle") {
44531
+ if (this.running && !this.currentFocus && (this.state === "idle" || this.state === "deciding")) {
44344
44532
  try {
44345
44533
  const cleaned = this.mailbox.cleanStaleProcessing();
44346
44534
  if (cleaned > 0) {
@@ -44408,6 +44596,9 @@ var init_attention = __esm({
44408
44596
  try {
44409
44597
  this.setState("deciding");
44410
44598
  const isUserMessage = _AttentionController.USER_INTERACTION_TYPES.has(item.sourceType);
44599
+ if (!isUserMessage && MAILBOX_COALESCE_WINDOW_MS > 0 && this.mailbox.depth > 0) {
44600
+ await new Promise((r) => setTimeout(r, MAILBOX_COALESCE_WINDOW_MS));
44601
+ }
44411
44602
  if (this.mailbox.depth > 0 && !isUserMessage) {
44412
44603
  this.mailbox.putBack(item);
44413
44604
  const purged = this.mailbox.purgeStaleItems();
@@ -44434,14 +44625,23 @@ var init_attention = __esm({
44434
44625
  deliberationAttempted = true;
44435
44626
  const allItems = [item, ...this.mailbox.getQueuedItems()];
44436
44627
  this.isDeliberating = true;
44628
+ this.deliberationAbortSignal = false;
44437
44629
  try {
44438
44630
  const deliberationResult = await this.delegate.performDeliberation(item, allItems);
44439
- if (deliberationResult) {
44631
+ if (this.deliberationAbortSignal) {
44632
+ log14.info("Deliberation aborted: human_chat arrived", { agentId: this.agentId });
44633
+ this.mailbox.putBack(item);
44634
+ const userItem = this.mailbox.dequeue();
44635
+ if (userItem) {
44636
+ item = userItem;
44637
+ }
44638
+ } else if (deliberationResult) {
44440
44639
  triaged = true;
44441
44640
  item = this.applyDeliberationResult(item, deliberationResult);
44442
44641
  }
44443
44642
  } finally {
44444
44643
  this.isDeliberating = false;
44644
+ this.deliberationAbortSignal = false;
44445
44645
  }
44446
44646
  } else {
44447
44647
  deliberationAttempted = true;
@@ -44515,10 +44715,14 @@ var init_attention = __esm({
44515
44715
  this.lastYieldDecision = void 0;
44516
44716
  this.processingStartedAt = Date.now();
44517
44717
  this.delegate?.onFocusChanged(item);
44718
+ const batchItems = this.pendingBatchItems.length > 0 ? [...this.pendingBatchItems] : void 0;
44719
+ const batchContext = this.pendingBatchContext;
44720
+ this.pendingBatchItems = [];
44721
+ this.pendingBatchContext = void 0;
44518
44722
  let reply = void 0;
44519
44723
  let timedOut = false;
44520
44724
  try {
44521
- const processing = this.delegate?.processMailboxItem(item);
44725
+ const processing = this.delegate?.processMailboxItem(item, batchItems, batchContext);
44522
44726
  const backstopMs = this.waitingForHumanApproval ? APPROVAL_WAIT_TIMEOUT_MS : MAILBOX_PROCESSING_TIMEOUT_MS;
44523
44727
  const backstop = new Promise((resolve22) => setTimeout(() => resolve22(void 0), backstopMs));
44524
44728
  const result = await Promise.race([
@@ -44608,6 +44812,16 @@ var init_attention = __esm({
44608
44812
  });
44609
44813
  this.mailbox.complete(item.id);
44610
44814
  }
44815
+ if (batchItems && batchItems.length > 0) {
44816
+ const wasSuccessful = !timedOut && reply !== "[cancelled]" && reply !== "[preempted]" && this.lastYieldDecision !== "cancel" && this.lastYieldDecision !== "preempt";
44817
+ for (const bi of batchItems) {
44818
+ if (wasSuccessful) {
44819
+ this.mailbox.complete(bi.id);
44820
+ } else {
44821
+ this.mailbox.requeue(bi);
44822
+ }
44823
+ }
44824
+ }
44611
44825
  this.currentFocus = void 0;
44612
44826
  this.interruptSignal = false;
44613
44827
  this.pendingInterruptItem = void 0;
@@ -44627,6 +44841,9 @@ var init_attention = __esm({
44627
44841
  const peeked = this.mailbox.peek();
44628
44842
  if (peeked) {
44629
44843
  this.pendingInterruptItem = peeked;
44844
+ if (this.isDeliberating && _AttentionController.USER_INTERACTION_TYPES.has(peeked.sourceType)) {
44845
+ this.deliberationAbortSignal = true;
44846
+ }
44630
44847
  const wouldPreempt = this.currentFocus ? this.heuristicDecision(this.currentFocus, peeked) === "preempt" : false;
44631
44848
  if (wouldPreempt && this.criticalInterruptResolve) {
44632
44849
  this.criticalInterruptResolve();
@@ -44661,6 +44878,10 @@ var init_attention = __esm({
44661
44878
  clearLastYieldDecision() {
44662
44879
  this.lastYieldDecision = void 0;
44663
44880
  }
44881
+ /** True if a human_chat arrived during deliberation, signalling early abort. */
44882
+ get shouldAbortDeliberation() {
44883
+ return this.deliberationAbortSignal;
44884
+ }
44664
44885
  /**
44665
44886
  * Called from the Agent's tool loop at safe yield points (between LLM turns).
44666
44887
  * If an interrupt signal is pending, evaluates whether to continue or switch.
@@ -44755,6 +44976,9 @@ var init_attention = __esm({
44755
44976
  heuristicDecision(currentItem, newItem) {
44756
44977
  const isNewUserInteraction = _AttentionController.USER_INTERACTION_TYPES.has(newItem.sourceType);
44757
44978
  const isCurrentUserInteraction = _AttentionController.USER_INTERACTION_TYPES.has(currentItem.sourceType);
44979
+ if (newItem.sourceType === "human_chat" && currentItem.sourceType === "human_chat" && newItem.metadata?.senderId && newItem.metadata.senderId === currentItem.metadata?.senderId) {
44980
+ return "merge";
44981
+ }
44758
44982
  if (isNewUserInteraction && !isCurrentUserInteraction) {
44759
44983
  return "preempt";
44760
44984
  }
@@ -45020,6 +45244,9 @@ var init_attention = __esm({
45020
45244
  * Apply a DeliberationResult to the mailbox: handle inline completions, reorder, defer, drop.
45021
45245
  * Returns the item to process next.
45022
45246
  */
45247
+ /** Batch items dequeued for the current processing cycle (set by applyDeliberationResult). */
45248
+ pendingBatchItems = [];
45249
+ pendingBatchContext;
45023
45250
  applyDeliberationResult(currentItem, result) {
45024
45251
  let item = currentItem;
45025
45252
  const queueSnapshot = this.mailbox.getQueuedItems();
@@ -45035,12 +45262,13 @@ var init_attention = __esm({
45035
45262
  return true;
45036
45263
  return false;
45037
45264
  };
45265
+ const effectiveProcessIds = result.processItemIds && result.processItemIds.length > 0 ? result.processItemIds : [result.processItemId];
45038
45266
  for (const completedId of result.inlineCompletedIds) {
45039
45267
  if (isProtected(completedId))
45040
45268
  continue;
45041
45269
  if (completedId === currentItem.id)
45042
45270
  continue;
45043
- if (completedId === result.processItemId)
45271
+ if (effectiveProcessIds.includes(completedId))
45044
45272
  continue;
45045
45273
  const completedItem = queueSnapshot.find((i) => i.id === completedId);
45046
45274
  this.mailbox.complete(completedId);
@@ -45048,15 +45276,16 @@ var init_attention = __esm({
45048
45276
  this.recordDecision("complete", completedItem, "Handled inline during deliberation");
45049
45277
  }
45050
45278
  }
45051
- if (result.processItemId !== item.id) {
45279
+ const primaryId = effectiveProcessIds[0];
45280
+ if (primaryId !== item.id) {
45052
45281
  this.mailbox.putBack(item);
45053
- const chosen = this.mailbox.dequeueById(result.processItemId);
45282
+ const chosen = this.mailbox.dequeueById(primaryId);
45054
45283
  if (chosen) {
45055
45284
  item = chosen;
45056
45285
  } else {
45057
45286
  log14.warn("Deliberation chose item that is no longer in queue, falling back to head", {
45058
45287
  agentId: this.agentId,
45059
- chosenId: result.processItemId,
45288
+ chosenId: primaryId,
45060
45289
  fallbackId: currentItem.id
45061
45290
  });
45062
45291
  const redequeued = this.mailbox.dequeueById(currentItem.id);
@@ -45064,6 +45293,19 @@ var init_attention = __esm({
45064
45293
  item = redequeued;
45065
45294
  }
45066
45295
  }
45296
+ this.pendingBatchItems = [];
45297
+ this.pendingBatchContext = result.batchContext;
45298
+ if (effectiveProcessIds.length > 1) {
45299
+ for (let i = 1; i < effectiveProcessIds.length; i++) {
45300
+ const batchId = effectiveProcessIds[i];
45301
+ if (isProtected(batchId))
45302
+ continue;
45303
+ const batchItem = this.mailbox.dequeueById(batchId);
45304
+ if (batchItem) {
45305
+ this.pendingBatchItems.push(batchItem);
45306
+ }
45307
+ }
45308
+ }
45067
45309
  for (const deferId of result.deferItemIds) {
45068
45310
  if (isProtected(deferId))
45069
45311
  continue;
@@ -45074,8 +45316,11 @@ var init_attention = __esm({
45074
45316
  continue;
45075
45317
  this.mailbox.drop(dropId);
45076
45318
  }
45319
+ if (result.memoryUpdates && result.memoryUpdates.length > 0) {
45320
+ this.delegate?.applyMemoryUpdates?.(result.memoryUpdates);
45321
+ }
45077
45322
  const triageEquivalent = {
45078
- processItemId: result.processItemId,
45323
+ processItemId: primaryId,
45079
45324
  deferItemIds: result.deferItemIds,
45080
45325
  dropItemIds: result.dropItemIds,
45081
45326
  inlineCompletedIds: result.inlineCompletedIds,
@@ -45191,6 +45436,7 @@ var init_attention = __esm({
45191
45436
  const queued = this.mailbox.getQueuedItems();
45192
45437
  return {
45193
45438
  attentionState: this.state,
45439
+ isDeliberating: this.isDeliberating || void 0,
45194
45440
  currentFocus: this.currentFocus ? {
45195
45441
  mailboxItemId: this.currentFocus.id,
45196
45442
  type: this.currentFocus.sourceType,
@@ -46517,8 +46763,10 @@ var init_agent2 = __esm({
46517
46763
  onActivityStartCb;
46518
46764
  onActivityLogCb;
46519
46765
  onActivityEndCb;
46766
+ browserCloseTabsHelper;
46520
46767
  dynamicContextProviders = /* @__PURE__ */ new Map();
46521
46768
  static MAX_ACTIVITY_LOG_ENTRIES = 200;
46769
+ static BROWSER_CLOSE_FOLLOWUP_MAX_ITER = 5;
46522
46770
  static MAX_ACTIVITY_LOGS_KEPT = 10;
46523
46771
  static MAX_CONCURRENT_TASKS = 1;
46524
46772
  static MAX_CONSECUTIVE_FAILURES = 3;
@@ -46528,6 +46776,9 @@ var init_agent2 = __esm({
46528
46776
  static NETWORK_RETRY_BASE_MS = 2e3;
46529
46777
  static MEMORY_CONSOLIDATION_INTERVAL_MS = 4 * 60 * 60 * 1e3;
46530
46778
  // 4 hours
46779
+ static MAX_CONCURRENT_DREAMS = 3;
46780
+ static dreamSemaphore = 0;
46781
+ static dreamQueue = [];
46531
46782
  static DEFAULT_MAX_TOOL_ITERATIONS = 200;
46532
46783
  static HEARTBEAT_MAX_TOOL_ITERATIONS = 30;
46533
46784
  /** Maps background_exec session IDs to the originating session that spawned them */
@@ -46702,17 +46953,48 @@ ${notification.stdoutTail}`);
46702
46953
  }
46703
46954
  this.heartbeat.start(options?.initialHeartbeatDelayMs);
46704
46955
  this.attentionController.start();
46705
- this.memoryConsolidationTimer = setInterval(() => {
46706
- this.consolidateMemory().catch((e) => log17.warn("Memory consolidation failed", { error: String(e) }));
46707
- }, _Agent.MEMORY_CONSOLIDATION_INTERVAL_MS);
46956
+ this.scheduleConsolidationTimer();
46708
46957
  this.eventBus.emit("agent:started", { agentId: this.id });
46709
46958
  log17.info(`Agent started: ${this.config.name}`);
46710
46959
  }
46960
+ consolidationInitialTimer;
46961
+ scheduleConsolidationTimer() {
46962
+ const jitter = Math.floor(Math.random() * _Agent.MEMORY_CONSOLIDATION_INTERVAL_MS);
46963
+ this.consolidationInitialTimer = setTimeout(() => {
46964
+ this.consolidationInitialTimer = void 0;
46965
+ this.consolidateMemory().catch((e) => log17.warn("Memory consolidation failed", { error: String(e) }));
46966
+ this.memoryConsolidationTimer = setInterval(() => {
46967
+ this.consolidateMemory().catch((e) => log17.warn("Memory consolidation failed", { error: String(e) }));
46968
+ }, _Agent.MEMORY_CONSOLIDATION_INTERVAL_MS);
46969
+ }, jitter);
46970
+ }
46971
+ static acquireDreamSlot() {
46972
+ if (_Agent.dreamSemaphore < _Agent.MAX_CONCURRENT_DREAMS) {
46973
+ _Agent.dreamSemaphore++;
46974
+ return Promise.resolve();
46975
+ }
46976
+ return new Promise((resolve22) => {
46977
+ _Agent.dreamQueue.push(() => {
46978
+ _Agent.dreamSemaphore++;
46979
+ resolve22();
46980
+ });
46981
+ });
46982
+ }
46983
+ static releaseDreamSlot() {
46984
+ _Agent.dreamSemaphore--;
46985
+ const next = _Agent.dreamQueue.shift();
46986
+ if (next)
46987
+ next();
46988
+ }
46711
46989
  async stop() {
46712
46990
  this.cancelActiveStream();
46713
46991
  this.attentionController.stop();
46714
46992
  this.heartbeat.stop();
46715
46993
  this._bgCompletionUnsub?.();
46994
+ if (this.consolidationInitialTimer) {
46995
+ clearTimeout(this.consolidationInitialTimer);
46996
+ this.consolidationInitialTimer = void 0;
46997
+ }
46716
46998
  if (this.memoryConsolidationTimer) {
46717
46999
  clearInterval(this.memoryConsolidationTimer);
46718
47000
  this.memoryConsolidationTimer = void 0;
@@ -46861,7 +47143,15 @@ ${notification.stdoutTail}`);
46861
47143
  }
46862
47144
  /** Get the current cognitive state of the agent. */
46863
47145
  getMindState() {
46864
- return this.attentionController.getMindState();
47146
+ const mind = this.attentionController.getMindState();
47147
+ if (mind.isDeliberating && this.state.currentActivity) {
47148
+ mind.deliberationActivity = {
47149
+ activityId: this.state.currentActivity.id,
47150
+ label: this.state.currentActivity.label,
47151
+ startedAt: this.state.currentActivity.startedAt
47152
+ };
47153
+ }
47154
+ return mind;
46865
47155
  }
46866
47156
  /** Get the raw mailbox instance (for persistence wiring). */
46867
47157
  getMailbox() {
@@ -46894,8 +47184,8 @@ ${notification.stdoutTail}`);
46894
47184
  */
46895
47185
  createAttentionDelegate() {
46896
47186
  return {
46897
- processMailboxItem: async (item) => {
46898
- return this.processMailboxItemInternal(item);
47187
+ processMailboxItem: async (item, batchItems, batchContext) => {
47188
+ return this.processMailboxItemInternal(item, batchItems, batchContext);
46899
47189
  },
46900
47190
  onDecisionMade: (decision) => {
46901
47191
  log17.debug("Attention decision", {
@@ -46943,6 +47233,15 @@ ${notification.stdoutTail}`);
46943
47233
  if (!result)
46944
47234
  return;
46945
47235
  this.updateCognitionFromDeliberation(result);
47236
+ },
47237
+ applyMemoryUpdates: (updates) => {
47238
+ for (const u of updates) {
47239
+ if (u.type === "working") {
47240
+ this.updateWorkingMemory(u.key, u.content);
47241
+ } else if (u.type === "longterm") {
47242
+ this.memory.addLongTermMemory(u.key, u.content);
47243
+ }
47244
+ }
46946
47245
  }
46947
47246
  };
46948
47247
  }
@@ -46950,9 +47249,31 @@ ${notification.stdoutTail}`);
46950
47249
  * Route a mailbox item to the appropriate Agent processing method.
46951
47250
  * Options like sessionId/images/scenario are forwarded from payload.extra
46952
47251
  * when present, falling back to type-appropriate defaults.
47252
+ * When batchItems are provided (from deliberation batch processing), their
47253
+ * content is composed into the primary item's message for unified handling.
46953
47254
  */
46954
- async processMailboxItemInternal(item) {
47255
+ async processMailboxItemInternal(item, batchItems, batchContext) {
46955
47256
  this.processingMailboxItemId = item.id;
47257
+ if (batchItems && batchItems.length > 0) {
47258
+ const batchHeader = batchContext ? `[BATCH PROCESSING \u2014 ${batchItems.length + 1} items]
47259
+ ${batchContext}
47260
+
47261
+ ---
47262
+
47263
+ ` : `[BATCH PROCESSING \u2014 ${batchItems.length + 1} items]
47264
+ Process the following items together and respond comprehensively.
47265
+
47266
+ ---
47267
+
47268
+ `;
47269
+ const batchSuffix = batchItems.map((bi, idx) => `
47270
+
47271
+ ---
47272
+ [Item ${idx + 2}] (${bi.sourceType}) ${bi.payload.summary}:
47273
+ ${bi.payload.content}`).join("");
47274
+ item.payload.content = batchHeader + `[Item 1] (${item.sourceType}) ${item.payload.summary}:
47275
+ ${item.payload.content}` + batchSuffix;
47276
+ }
46956
47277
  if (item.sourceType === "human_chat" && item.metadata?.senderId) {
46957
47278
  this.currentInteractingUserId = item.metadata.senderId;
46958
47279
  }
@@ -47285,6 +47606,10 @@ ${notification.stdoutTail}`);
47285
47606
  this.cancelActiveStream();
47286
47607
  this.heartbeat.stop();
47287
47608
  this.attentionController.stop();
47609
+ if (this.consolidationInitialTimer) {
47610
+ clearTimeout(this.consolidationInitialTimer);
47611
+ this.consolidationInitialTimer = void 0;
47612
+ }
47288
47613
  if (this.memoryConsolidationTimer) {
47289
47614
  clearInterval(this.memoryConsolidationTimer);
47290
47615
  this.memoryConsolidationTimer = void 0;
@@ -47301,9 +47626,7 @@ ${notification.stdoutTail}`);
47301
47626
  this.heartbeat.start();
47302
47627
  this.attentionController.start();
47303
47628
  if (!this.memoryConsolidationTimer) {
47304
- this.memoryConsolidationTimer = setInterval(() => {
47305
- this.consolidateMemory().catch((e) => log17.warn("Memory consolidation failed", { error: String(e) }));
47306
- }, _Agent.MEMORY_CONSOLIDATION_INTERVAL_MS);
47629
+ this.scheduleConsolidationTimer();
47307
47630
  }
47308
47631
  this.setStatus(this.activeTasks.size > 0 ? "working" : "idle");
47309
47632
  this.eventBus.emit("agent:resumed", { agentId: this.id });
@@ -47875,13 +48198,14 @@ ${conversationText}`
47875
48198
  "2. **Gather context**: `recall_activity`, `task_get`, `memory_search` for background.",
47876
48199
  "3. **Manage queue**: `defer_mailbox_item` to postpone, `drop_mailbox_item` to discard stale items.",
47877
48200
  "4. **Handle inline**: `notify_user`, `task_comment`, `agent_send_message` for trivial replies. Mark as inline_completed.",
47878
- "5. **Save awareness**: `update_working_memory` to record your assessment for future calls.",
48201
+ "5. **Save awareness**: `update_working_memory` or `memory_save` to record observations.",
47879
48202
  "6. **Decide**: `complete_deliberation` with your final focus decision.",
47880
48203
  "",
47881
48204
  "## Guidelines",
47882
48205
  "- Human messages (priority 0) always take precedence.",
47883
- "- Items sharing a taskId/requirementId/channel \u2192 batch handle with one context lookup.",
48206
+ "- **Batch processing**: Items sharing a taskId/requirementId/channel can be processed together \u2014 pass multiple IDs in `process_item_ids` and provide `batch_context` to guide unified handling.",
47884
48207
  "- Drop old informational items (heartbeats, status updates) aggressively.",
48208
+ "- **Memory updates**: Use `memory_updates` in `complete_deliberation` to record team decisions, observations, or context that should persist across cycles.",
47885
48209
  "- Your working memory persists across processing cycles \u2014 use it for continuity.",
47886
48210
  "",
47887
48211
  "When ready, call `complete_deliberation`."
@@ -48097,6 +48421,105 @@ ${instructions}
48097
48421
  this.onActivityLogCb = cbs.onLog;
48098
48422
  this.onActivityEndCb = cbs.onEnd;
48099
48423
  }
48424
+ setBrowserCloseTabsHelper(fn) {
48425
+ this.browserCloseTabsHelper = fn;
48426
+ }
48427
+ /**
48428
+ * At session end: inject a one-time [SYSTEM] reminder if the session used
48429
+ * the browser and still has owned tabs, then run a lightweight LLM turn so
48430
+ * the agent can call close_page.
48431
+ */
48432
+ async finalizeBrowserSession(sessionId) {
48433
+ if (!this.browserCloseTabsHelper)
48434
+ return;
48435
+ const reminder = this.browserCloseTabsHelper(sessionId);
48436
+ if (!reminder)
48437
+ return;
48438
+ try {
48439
+ this.memory.appendMessage(sessionId, { role: "user", content: reminder });
48440
+ log17.debug("Injected browser close-tabs reminder", { agentId: this.id, sessionId });
48441
+ const allTools = this.buildToolDefinitions({});
48442
+ const browserTools = allTools.filter((t) => t.name.startsWith("chrome-devtools__") && (t.name.endsWith("__close_page") || t.name.endsWith("__list_pages") || t.name.endsWith("__select_page")));
48443
+ if (browserTools.length === 0)
48444
+ return;
48445
+ const { text: systemPrompt, segments: systemCacheSegments } = await this.contextEngine.buildSystemPrompt({
48446
+ agentId: this.id,
48447
+ agentName: this.config.name,
48448
+ role: this.role,
48449
+ orgContext: this.orgContext,
48450
+ contextMdPath: this.contextMdPath,
48451
+ memory: this.memory,
48452
+ scenario: "chat",
48453
+ ...this.getTeamContextParams()
48454
+ });
48455
+ const useCompaction = this.llmRouter.isCompactionSupported(this.getEffectiveProvider());
48456
+ const prepareFollowUpMessages = () => this.contextEngine.prepareMessages({
48457
+ systemPrompt,
48458
+ sessionMessages: this.memory.getRecentMessages(sessionId, 50),
48459
+ memory: this.memory,
48460
+ sessionId,
48461
+ agentId: this.id,
48462
+ modelContextWindow: this.llmRouter.getModelContextWindow(this.getEffectiveProvider()),
48463
+ modelMaxOutput: this.llmRouter.getModelMaxOutput(this.getEffectiveProvider()),
48464
+ toolDefinitions: browserTools,
48465
+ systemCacheSegments
48466
+ });
48467
+ let iterations = 0;
48468
+ let preparedFollowUp = await prepareFollowUpMessages();
48469
+ let response = await this.withNetworkRetry(() => this.llmRouter.chat({
48470
+ messages: preparedFollowUp.messages,
48471
+ tools: browserTools,
48472
+ metadata: this.getLLMMetadata(sessionId),
48473
+ compaction: useCompaction,
48474
+ systemCacheSegments
48475
+ }, this.getEffectiveProvider()), "Browser close-tabs follow-up");
48476
+ while (response.finishReason === "tool_use" && response.toolCalls?.length && ++iterations <= _Agent.BROWSER_CLOSE_FOLLOWUP_MAX_ITER) {
48477
+ this.memory.appendMessage(sessionId, {
48478
+ role: "assistant",
48479
+ content: response.content,
48480
+ toolCalls: response.toolCalls,
48481
+ reasoningContent: response.reasoningContent
48482
+ });
48483
+ for (const tc of response.toolCalls) {
48484
+ try {
48485
+ let result = await this.executeTool(tc, void 0, sessionId);
48486
+ result = this.offloadLargeResult(tc.name, result);
48487
+ this.memory.appendMessage(sessionId, { role: "tool", content: result, toolCallId: tc.id });
48488
+ } catch (toolErr) {
48489
+ this.memory.appendMessage(sessionId, {
48490
+ role: "tool",
48491
+ content: `Error: ${String(toolErr)}`,
48492
+ toolCallId: tc.id
48493
+ });
48494
+ }
48495
+ }
48496
+ preparedFollowUp = await prepareFollowUpMessages();
48497
+ response = await this.withNetworkRetry(() => this.llmRouter.chat({
48498
+ messages: preparedFollowUp.messages,
48499
+ tools: browserTools,
48500
+ metadata: this.getLLMMetadata(sessionId),
48501
+ compaction: useCompaction,
48502
+ systemCacheSegments
48503
+ }, this.getEffectiveProvider()), "Browser close-tabs follow-up continuation");
48504
+ }
48505
+ if (response.toolCalls?.length) {
48506
+ this.memory.appendMessage(sessionId, {
48507
+ role: "assistant",
48508
+ content: response.content,
48509
+ toolCalls: response.toolCalls,
48510
+ reasoningContent: response.reasoningContent
48511
+ });
48512
+ } else if (response.content?.trim()) {
48513
+ this.memory.appendMessage(sessionId, {
48514
+ role: "assistant",
48515
+ content: sanitizeLLMReply(response.content),
48516
+ reasoningContent: response.reasoningContent
48517
+ });
48518
+ }
48519
+ } catch (err) {
48520
+ log17.warn("Browser close-tabs follow-up failed", { agentId: this.id, sessionId, error: String(err) });
48521
+ }
48522
+ }
48100
48523
  // ─── Activity Tracking ───────────────────────────────────────────────────────
48101
48524
  startActivity(type, label, extra) {
48102
48525
  const id = `act-${this.id}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
@@ -48870,6 +49293,8 @@ ${chatYield.item.payload.content}`;
48870
49293
  });
48871
49294
  log17.error("Failed to handle message", { error: String(error) });
48872
49295
  throw error;
49296
+ } finally {
49297
+ await this.finalizeBrowserSession(sessionId);
48873
49298
  }
48874
49299
  }
48875
49300
  async handleMessageStream(userMessage, onEvent, senderId, senderInfo, cancelToken, images, fileNames) {
@@ -49247,6 +49672,9 @@ ${streamYield.item.payload.content}`;
49247
49672
  } finally {
49248
49673
  if (cancelPollTimer)
49249
49674
  clearInterval(cancelPollTimer);
49675
+ if (this.currentSessionId) {
49676
+ await this.finalizeBrowserSession(this.currentSessionId);
49677
+ }
49250
49678
  }
49251
49679
  }
49252
49680
  /**
@@ -49800,6 +50228,7 @@ ${yieldResult.item.payload.content}`
49800
50228
  this.activeTasks.delete(taskId2);
49801
50229
  this.activeTaskGen.delete(taskId2);
49802
50230
  this.pendingInjections.delete(sessionId);
50231
+ await this.finalizeBrowserSession(sessionId);
49803
50232
  }
49804
50233
  this.endActivity(taskActId);
49805
50234
  this.notifyStateChange();
@@ -50014,6 +50443,7 @@ ${risYield.item.payload.content}`
50014
50443
  this.endActivity(actId);
50015
50444
  if (this.activeTasks.size === 0)
50016
50445
  this.setStatus("idle");
50446
+ await this.finalizeBrowserSession(sessionId);
50017
50447
  }
50018
50448
  }
50019
50449
  skillProficiency = /* @__PURE__ */ new Map();
@@ -50397,16 +50827,20 @@ ${body}${contextSuffix}`;
50397
50827
  }
50398
50828
  if (toolCall.name === "complete_deliberation") {
50399
50829
  const processItemId = toolCall.arguments.process_item_id;
50400
- if (!processItemId) {
50401
- return JSON.stringify({ status: "error", message: "process_item_id is required" });
50830
+ const processItemIds = toolCall.arguments.process_item_ids;
50831
+ if (!processItemId && (!processItemIds || processItemIds.length === 0)) {
50832
+ return JSON.stringify({ status: "error", message: "process_item_id or process_item_ids is required" });
50402
50833
  }
50403
50834
  this.pendingDeliberationResult = {
50404
- processItemId,
50835
+ processItemId: processItemId || processItemIds[0],
50836
+ processItemIds: processItemIds && processItemIds.length > 1 ? processItemIds : void 0,
50837
+ batchContext: toolCall.arguments.batch_context,
50405
50838
  deferItemIds: toolCall.arguments.defer_item_ids ?? [],
50406
50839
  dropItemIds: toolCall.arguments.drop_item_ids ?? [],
50407
50840
  inlineCompletedIds: toolCall.arguments.inline_completed_ids ?? [],
50408
50841
  reasoning: toolCall.arguments.reasoning ?? "",
50409
- situationalAwareness: toolCall.arguments.situational_awareness
50842
+ situationalAwareness: toolCall.arguments.situational_awareness,
50843
+ memoryUpdates: toolCall.arguments.memory_updates
50410
50844
  };
50411
50845
  return JSON.stringify({ status: "ok", message: "Deliberation decision recorded. Proceeding to focused processing." });
50412
50846
  }
@@ -50954,8 +51388,13 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
50954
51388
  const dreamKey = entries2.length > 500 ? `${today}_${Math.floor(Date.now() / (6 * 36e5))}` : today;
50955
51389
  if (entries2.length >= 50 && this.lastDreamDate !== dreamKey) {
50956
51390
  this.lastDreamDate = dreamKey;
50957
- await this.dreamConsolidateMemory(entries2);
50958
- this.pruneMemoryMd();
51391
+ await _Agent.acquireDreamSlot();
51392
+ try {
51393
+ await this.dreamConsolidateMemory(entries2);
51394
+ this.pruneMemoryMd();
51395
+ } finally {
51396
+ _Agent.releaseDreamSlot();
51397
+ }
50959
51398
  }
50960
51399
  log17.debug("Memory consolidation completed", { agentId: this.id });
50961
51400
  } catch (error) {
@@ -51785,6 +52224,10 @@ var init_browser_session = __esm({
51785
52224
  * route to the new process after reconnect.
51786
52225
  */
51787
52226
  reconnectors = /* @__PURE__ */ new Map();
52227
+ /** ownerKeys where a browser tool succeeded (excluding close_page / list_pages). */
52228
+ browserUsedKeys = /* @__PURE__ */ new Set();
52229
+ /** ownerKeys that already received a session-end close-tabs reminder. */
52230
+ remindedKeys = /* @__PURE__ */ new Set();
51788
52231
  _bringToFront = false;
51789
52232
  _autoCloseTabs = true;
51790
52233
  get bringToFront() {
@@ -51971,6 +52414,56 @@ var init_browser_session = __esm({
51971
52414
  const ids = [...owned].join(", ");
51972
52415
  return `Your owned tab IDs: [${ids}] (${owned.size} total). You can only operate on these.`;
51973
52416
  }
52417
+ ownerKeyFor(agentId2, sessionId) {
52418
+ return sessionId ? `${agentId2}::${sessionId}` : agentId2;
52419
+ }
52420
+ isToolError(result) {
52421
+ if (this.isStalePageError(result))
52422
+ return true;
52423
+ if (result.startsWith("Cannot ") || result.startsWith("Failed to auto-create"))
52424
+ return true;
52425
+ try {
52426
+ const parsed = JSON.parse(result);
52427
+ if (parsed?.error)
52428
+ return true;
52429
+ } catch {
52430
+ }
52431
+ return false;
52432
+ }
52433
+ /** Mark that this session successfully used the browser (called from tool wrappers). */
52434
+ markBrowserUsed(ownerKey) {
52435
+ this.browserUsedKeys.add(ownerKey);
52436
+ }
52437
+ markBrowserUsedIfSuccess(ownerKey, result) {
52438
+ if (!this.isToolError(result)) {
52439
+ this.markBrowserUsed(ownerKey);
52440
+ }
52441
+ }
52442
+ /** Return owned tab IDs for an agent session (for reminder text). */
52443
+ getOwnedTabIds(agentId2, sessionId) {
52444
+ const ownerKey = this.ownerKeyFor(agentId2, sessionId);
52445
+ return [...this.getOwned(ownerKey)];
52446
+ }
52447
+ /**
52448
+ * Consume a one-time session-end reminder if the session used the browser,
52449
+ * has remaining owned tabs, and has not been reminded yet.
52450
+ */
52451
+ consumeCloseTabsReminder(agentId2, sessionId) {
52452
+ const ownerKey = this.ownerKeyFor(agentId2, sessionId);
52453
+ if (!this.browserUsedKeys.has(ownerKey))
52454
+ return null;
52455
+ if (this.remindedKeys.has(ownerKey))
52456
+ return null;
52457
+ const owned = this.getOwned(ownerKey);
52458
+ if (owned.size === 0)
52459
+ return null;
52460
+ this.remindedKeys.add(ownerKey);
52461
+ const ids = [...owned].join(", ");
52462
+ return [
52463
+ "[SYSTEM] You used the browser in this session. Before finishing, close any tabs you no longer need with chrome-devtools__close_page.",
52464
+ `Your owned tab IDs: [${ids}] (${owned.size} total).`
52465
+ ].join(" ");
52466
+ }
51974
52467
  // ─── Public API ───────────────────────────────────────────────────────────
51975
52468
  wrapToolHandlers(handlers, agentId2) {
51976
52469
  const findHandler = (name) => handlers.find((h) => (h.name.split("__").pop() ?? h.name) === name);
@@ -52037,6 +52530,7 @@ var init_browser_session = __esm({
52037
52530
  } else {
52038
52531
  log19.warn(`new_page response contained no pages for ${ownerKey}`);
52039
52532
  }
52533
+ this.markBrowserUsedIfSuccess(ownerKey, result);
52040
52534
  }
52041
52535
  return this.annotateResponse(result, ownerKey);
52042
52536
  });
@@ -52095,6 +52589,7 @@ var init_browser_session = __esm({
52095
52589
  this.currentPage.set(ownerKey, pageId);
52096
52590
  this.lastActiveSession.set(agentId2, { ownerKey, pageId });
52097
52591
  }
52592
+ this.markBrowserUsedIfSuccess(ownerKey, result);
52098
52593
  return this.annotateResponse(result, ownerKey);
52099
52594
  });
52100
52595
  }
@@ -52164,6 +52659,7 @@ var init_browser_session = __esm({
52164
52659
  return this.navigateAutoCreateLocked(agentId2, ownerKey, args, newPageHandler);
52165
52660
  }
52166
52661
  }
52662
+ this.markBrowserUsedIfSuccess(ownerKey, result);
52167
52663
  return this.annotateResponse(result, ownerKey);
52168
52664
  });
52169
52665
  }
@@ -52209,6 +52705,7 @@ var init_browser_session = __esm({
52209
52705
  log19.info(`Auto-created page ${newPage.id} (${newPage.url}) for ${ownerKey}`);
52210
52706
  }
52211
52707
  }
52708
+ this.markBrowserUsedIfSuccess(ownerKey, result);
52212
52709
  return this.annotateResponse(result, ownerKey);
52213
52710
  } catch (err) {
52214
52711
  return JSON.stringify({ error: `Failed to auto-create new tab: ${err}` });
@@ -52250,6 +52747,7 @@ var init_browser_session = __esm({
52250
52747
  return `The tab you were operating on was closed externally. ${this.ownedPagesSummary(ownerKey)} Call navigate_page or new_page to create a new tab, then retry.`;
52251
52748
  }
52252
52749
  }
52750
+ this.markBrowserUsedIfSuccess(ownerKey, result);
52253
52751
  return this.annotateResponse(result, ownerKey);
52254
52752
  });
52255
52753
  }
@@ -52266,6 +52764,16 @@ var init_browser_session = __esm({
52266
52764
  this.currentPage.delete(key2);
52267
52765
  }
52268
52766
  }
52767
+ for (const key2 of [...this.browserUsedKeys]) {
52768
+ if (key2 === agentId2 || key2.startsWith(prefix)) {
52769
+ this.browserUsedKeys.delete(key2);
52770
+ }
52771
+ }
52772
+ for (const key2 of [...this.remindedKeys]) {
52773
+ if (key2 === agentId2 || key2.startsWith(prefix)) {
52774
+ this.remindedKeys.delete(key2);
52775
+ }
52776
+ }
52269
52777
  this.agentLocks.delete(agentId2);
52270
52778
  this.selectPageHandlers.delete(agentId2);
52271
52779
  this.listPageHandlers.delete(agentId2);
@@ -52320,7 +52828,7 @@ function createPackageTools(ctx) {
52320
52828
  },
52321
52829
  {
52322
52830
  name: "package_install",
52323
- description: 'Install a package into the live organization. type "agent": hire/install an agent (from a built-in role or a custom package). type "team": deploy a full team with all members, norms, and starter tasks. type "skill": install a skill package. Use package_list to see what is available.',
52831
+ description: 'Install a package into the live organization. type "agent": hire/install an agent (from a built-in role or a custom package). type "team": deploy a full team with all members, norms, and starter tasks. type "skill": install a skill package. Requires user approval. Use package_list to see what is available.',
52324
52832
  inputSchema: {
52325
52833
  type: "object",
52326
52834
  properties: {
@@ -52350,6 +52858,15 @@ function createPackageTools(ctx) {
52350
52858
  return JSON.stringify({ status: "error", error: "type is required and must be one of: agent, team, skill" });
52351
52859
  if (!name)
52352
52860
  return JSON.stringify({ status: "error", error: "name is required \u2014 use package_list to see available packages" });
52861
+ if (ctx.requestApproval) {
52862
+ const { approved, comment } = await ctx.requestApproval({
52863
+ toolName: "package_install",
52864
+ toolArgs: { type, name, agent_name: args["agent_name"] },
52865
+ reason: `Agent wants to install ${type} "${name}" into the organization`
52866
+ });
52867
+ if (!approved)
52868
+ return JSON.stringify({ status: "rejected", reason: comment || "User denied package installation" });
52869
+ }
52353
52870
  if (type === "agent") {
52354
52871
  try {
52355
52872
  const result2 = await installArtifact(type, name);
@@ -52422,7 +52939,7 @@ function createPackageTools(ctx) {
52422
52939
  },
52423
52940
  {
52424
52941
  name: "hub_install",
52425
- description: "Download an item from Markus Hub and install it. Combines download, save as artifact, and install into a single step. For agents/teams: remember to onboard the new agent(s) after installation.",
52942
+ description: "Download an item from Markus Hub and install it. Combines download, save as artifact, and install into a single step. Requires user approval. For agents/teams: remember to onboard the new agent(s) after installation.",
52426
52943
  inputSchema: {
52427
52944
  type: "object",
52428
52945
  properties: {
@@ -52438,6 +52955,15 @@ function createPackageTools(ctx) {
52438
52955
  const itemId = args["item_id"]?.trim();
52439
52956
  if (!itemId)
52440
52957
  return JSON.stringify({ status: "error", error: "item_id is required \u2014 use hub_search to find available items first" });
52958
+ if (ctx.requestApproval) {
52959
+ const { approved, comment } = await ctx.requestApproval({
52960
+ toolName: "hub_install",
52961
+ toolArgs: { item_id: itemId },
52962
+ reason: `Agent wants to download and install Hub item "${itemId}" into the organization`
52963
+ });
52964
+ if (!approved)
52965
+ return JSON.stringify({ status: "rejected", reason: comment || "User denied Hub installation" });
52966
+ }
52441
52967
  const result = await downloadAndInstall(itemId);
52442
52968
  const isAgentOrTeam = result.type === "agent" || result.type === "team";
52443
52969
  return JSON.stringify({
@@ -52653,11 +53179,10 @@ function createA2ATools(ctx) {
52653
53179
  name: "agent_send_message",
52654
53180
  description: [
52655
53181
  "Send a message to another agent (colleague) in your organization.",
52656
- "This tool is for STATUS NOTIFICATIONS, QUICK COORDINATION, and SIMPLE QUESTIONS only.",
52657
- "Two modes: (1) wait_for_reply=true \u2014 block until the target agent responds, then return their reply.",
52658
- "Use this when you need feedback, an answer, or a decision (e.g., asking a question, requesting a review opinion).",
52659
- "(2) wait_for_reply=false (default) \u2014 fire-and-forget notification.",
52660
- 'Use this for one-way announcements (e.g., "I submitted task X for review", status updates).',
53182
+ "This tool is ALWAYS asynchronous (fire-and-forget): the message enters the target agent's mailbox and you continue working.",
53183
+ "The recipient will process it on their own schedule and may reply via their own agent_send_message.",
53184
+ "Use conversation_id to correlate multi-turn exchanges \u2014 you and the recipient will both see this ID.",
53185
+ "Record what you asked in your working memory so you recognize the reply when it arrives.",
52661
53186
  "IMPORTANT: Do NOT use this tool to request substantial work from another agent.",
52662
53187
  "If you need another agent to perform multi-step work, file changes, or extended execution,",
52663
53188
  "use requirement_propose + task_create instead \u2014 tasks provide tracking, review, and audit trail."
@@ -52667,9 +53192,13 @@ function createA2ATools(ctx) {
52667
53192
  properties: {
52668
53193
  agent_id: { type: "string", description: "The ID of the agent to message" },
52669
53194
  message: { type: "string", description: "The message to send" },
53195
+ conversation_id: {
53196
+ type: "string",
53197
+ description: "Optional correlation ID for multi-turn exchanges. Auto-generated if omitted. Include this when replying to a previous message to maintain context."
53198
+ },
52670
53199
  wait_for_reply: {
52671
53200
  type: "boolean",
52672
- description: "If true, wait for the target agent to process the message and return their reply. Use for questions/requests. Default: false (notification mode)."
53201
+ description: "[DEPRECATED \u2014 ignored] A2A is always async. Record your question in working memory and process the reply when it arrives."
52673
53202
  }
52674
53203
  },
52675
53204
  required: ["agent_id", "message"]
@@ -52677,26 +53206,25 @@ function createA2ATools(ctx) {
52677
53206
  async execute(args) {
52678
53207
  const targetId = args["agent_id"];
52679
53208
  const message = args["message"];
52680
- const waitForReply = args["wait_for_reply"] ?? false;
53209
+ const conversationId = args["conversation_id"] || generateId("conv");
53210
+ const waitForReply = args["wait_for_reply"];
52681
53211
  if (targetId === ctx.selfId) {
52682
53212
  return JSON.stringify({ status: "error", error: "Cannot send a message to yourself" });
52683
53213
  }
52684
53214
  if (waitForReply) {
52685
- log21.info(`A2A request (sync): ${ctx.selfName} \u2192 ${targetId}`, { messageLen: message.length });
52686
- try {
52687
- const reply = await ctx.sendMessage(targetId, message, ctx.selfId, ctx.selfName, 0, true);
52688
- log21.info(`A2A reply received: ${targetId} \u2192 ${ctx.selfName}`, { replyLen: reply.length });
52689
- return JSON.stringify({ status: "replied", from: targetId, reply });
52690
- } catch (err) {
52691
- log21.warn(`A2A sync message to ${targetId} failed`, { error: String(err) });
52692
- return JSON.stringify({ status: "error", error: `Failed to get reply: ${String(err)}` });
52693
- }
53215
+ log21.warn(`A2A wait_for_reply=true is deprecated and ignored (deadlock risk). Sending async. Sender: ${ctx.selfName} \u2192 ${targetId}`);
52694
53216
  }
52695
- log21.info(`A2A notify (async): ${ctx.selfName} \u2192 ${targetId}`, { messageLen: message.length });
52696
- ctx.sendMessage(targetId, message, ctx.selfId, ctx.selfName, void 0, false).catch((err) => {
53217
+ const taggedMessage = `[conversation:${conversationId}]
53218
+ ${message}`;
53219
+ log21.info(`A2A send (async): ${ctx.selfName} \u2192 ${targetId}`, { messageLen: message.length, conversationId });
53220
+ ctx.sendMessage(targetId, taggedMessage, ctx.selfId, ctx.selfName, void 0, false).catch((err) => {
52697
53221
  log21.warn(`A2A async message to ${targetId} failed in background`, { error: String(err) });
52698
53222
  });
52699
- return JSON.stringify({ status: "dispatched", message: "Notification sent. The agent will process it independently." });
53223
+ return JSON.stringify({
53224
+ status: "dispatched",
53225
+ conversation_id: conversationId,
53226
+ message: "Message sent asynchronously. The agent will process it on their schedule. Record your question in working memory to recognize the reply."
53227
+ });
52700
53228
  }
52701
53229
  },
52702
53230
  {
@@ -54255,35 +54783,62 @@ function createProjectTools(ctx) {
54255
54783
  },
54256
54784
  {
54257
54785
  name: "get_project",
54258
- description: "Get detailed information about a specific project including repositories, teams, and governance policy.",
54786
+ description: "Get detailed information about a specific project including repositories (with paths and branches), teams, and governance policy. Use project_id or omit to get your current project.",
54259
54787
  inputSchema: {
54260
54788
  type: "object",
54261
54789
  properties: {
54262
54790
  project_id: {
54263
54791
  type: "string",
54264
- description: "The project ID"
54792
+ description: "The project ID (omit to get current project if available)"
54265
54793
  }
54266
- },
54267
- required: ["project_id"]
54794
+ }
54268
54795
  },
54269
54796
  async execute(args) {
54270
54797
  try {
54271
54798
  const projectId = args["project_id"];
54272
- const project = ctx.projectService.getProject(projectId);
54273
- if (!project)
54274
- return JSON.stringify({ status: "error", error: "Project not found" });
54275
- return JSON.stringify({
54276
- status: "success",
54277
- project: {
54278
- id: project.id,
54279
- name: project.name,
54280
- description: project.description,
54281
- status: project.status,
54282
- repoCount: project.repositories?.length ?? 0,
54283
- teamIds: project.teamIds ?? [],
54284
- governance: project.governancePolicy?.enabled ? project.governancePolicy.defaultTier : "none"
54285
- }
54286
- });
54799
+ if (projectId) {
54800
+ const project = ctx.projectService.getProject(projectId);
54801
+ if (!project)
54802
+ return JSON.stringify({ status: "error", error: "Project not found" });
54803
+ return JSON.stringify({
54804
+ status: "success",
54805
+ project: {
54806
+ id: project.id,
54807
+ name: project.name,
54808
+ description: project.description,
54809
+ status: project.status,
54810
+ repositories: (project.repositories ?? []).map((r) => ({
54811
+ path: r.localPath,
54812
+ branch: r.defaultBranch,
54813
+ role: r.role
54814
+ })),
54815
+ teamIds: project.teamIds ?? [],
54816
+ governancePolicy: project.governancePolicy ?? null
54817
+ }
54818
+ });
54819
+ }
54820
+ if (ctx.getProjectInfo) {
54821
+ const info4 = await ctx.getProjectInfo();
54822
+ if (!info4)
54823
+ return JSON.stringify({ status: "error", error: "No current project found" });
54824
+ return JSON.stringify({
54825
+ status: "success",
54826
+ project: {
54827
+ id: info4.id,
54828
+ name: info4.name,
54829
+ description: info4.description,
54830
+ status: info4.status,
54831
+ repositories: (info4.repositories ?? []).map((r) => ({
54832
+ path: r.localPath,
54833
+ branch: r.defaultBranch,
54834
+ role: r.role
54835
+ })),
54836
+ teamIds: info4.teamIds ?? [],
54837
+ governancePolicy: info4.governancePolicy ?? null
54838
+ }
54839
+ });
54840
+ }
54841
+ return JSON.stringify({ status: "error", error: "project_id is required" });
54287
54842
  } catch (error) {
54288
54843
  return JSON.stringify({ status: "error", error: String(error) });
54289
54844
  }
@@ -54330,14 +54885,40 @@ function createProjectTools(ctx) {
54330
54885
  ...ctx.projectService?.updateProject ? [
54331
54886
  {
54332
54887
  name: "update_project",
54333
- description: "Update an existing project (name, description, or status). Requires user approval before execution.",
54888
+ description: "Update an existing project. Supports name, description, status, repositories, teamIds, and governancePolicy. Changing description alone executes directly; all other changes require user approval.",
54334
54889
  inputSchema: {
54335
54890
  type: "object",
54336
54891
  properties: {
54337
54892
  project_id: { type: "string", description: "The project ID to update" },
54338
54893
  name: { type: "string", description: "New project name (optional)" },
54339
54894
  description: { type: "string", description: "New description (optional)" },
54340
- status: { type: "string", enum: ["active", "paused", "archived"], description: "New status (optional)" }
54895
+ status: { type: "string", enum: ["active", "paused", "archived"], description: "New status (optional)" },
54896
+ repositories: {
54897
+ type: "array",
54898
+ items: {
54899
+ type: "object",
54900
+ properties: {
54901
+ localPath: { type: "string", description: "Local filesystem path to the repository" },
54902
+ defaultBranch: { type: "string", description: "Default branch name (e.g. main)" },
54903
+ role: { type: "string", enum: ["primary", "secondary"], description: "Role of this repository" }
54904
+ },
54905
+ required: ["localPath", "defaultBranch", "role"]
54906
+ },
54907
+ description: "Full list of repositories (replaces existing). Requires approval."
54908
+ },
54909
+ team_ids: {
54910
+ type: "array",
54911
+ items: { type: "string" },
54912
+ description: "Team IDs to associate with this project. Requires approval."
54913
+ },
54914
+ governance_policy: {
54915
+ type: "object",
54916
+ properties: {
54917
+ enabled: { type: "boolean" },
54918
+ defaultTier: { type: "string" }
54919
+ },
54920
+ description: "Governance policy settings. Requires approval."
54921
+ }
54341
54922
  },
54342
54923
  required: ["project_id"]
54343
54924
  },
@@ -54351,11 +54932,20 @@ function createProjectTools(ctx) {
54351
54932
  data.description = args["description"];
54352
54933
  if (args["status"])
54353
54934
  data.status = args["status"];
54354
- if (ctx.requestApproval) {
54935
+ if (args["repositories"])
54936
+ data.repositories = args["repositories"];
54937
+ if (args["team_ids"])
54938
+ data.teamIds = args["team_ids"];
54939
+ if (args["governance_policy"])
54940
+ data.governancePolicy = args["governance_policy"];
54941
+ const APPROVAL_FIELDS = ["name", "status", "repositories", "teamIds", "governancePolicy"];
54942
+ const needsApproval = APPROVAL_FIELDS.some((f) => data[f] !== void 0);
54943
+ if (needsApproval && ctx.requestApproval) {
54944
+ const changedFields = Object.keys(data).filter((k) => k !== "description");
54355
54945
  const { approved, comment } = await ctx.requestApproval({
54356
54946
  toolName: "update_project",
54357
54947
  toolArgs: { project_id: projectId, ...data },
54358
- reason: `Agent wants to update project ${projectId}`
54948
+ reason: `Agent wants to update project ${projectId} (fields: ${changedFields.join(", ")})`
54359
54949
  });
54360
54950
  if (!approved)
54361
54951
  return JSON.stringify({ status: "rejected", reason: comment || "User denied project update" });
@@ -54369,36 +54959,56 @@ function createProjectTools(ctx) {
54369
54959
  }
54370
54960
  ] : []
54371
54961
  ] : [],
54372
- ...ctx.getProjectInfo ? [
54962
+ ...ctx.projectService?.deleteProject ? [
54373
54963
  {
54374
- name: "project_info",
54375
- description: "Get details about your current project: repositories, governance rules, and team composition. Call this when you need to understand your working context.",
54964
+ name: "delete_project",
54965
+ description: "Delete a project. This is a destructive operation that unlinks all tasks and requirements. Always requires user approval.",
54376
54966
  inputSchema: {
54377
54967
  type: "object",
54378
54968
  properties: {
54379
- project_id: {
54380
- type: "string",
54381
- description: "Project ID (omit for your current project)"
54969
+ project_id: { type: "string", description: "The project ID to delete" },
54970
+ reason: { type: "string", description: "Why this project should be deleted" }
54971
+ },
54972
+ required: ["project_id", "reason"]
54973
+ },
54974
+ async execute(args) {
54975
+ try {
54976
+ const projectId = args["project_id"];
54977
+ const reason = args["reason"];
54978
+ if (ctx.requestApproval) {
54979
+ const { approved, comment } = await ctx.requestApproval({
54980
+ toolName: "delete_project",
54981
+ toolArgs: { project_id: projectId, reason },
54982
+ reason: `Agent wants to DELETE project ${projectId}: ${reason}`
54983
+ });
54984
+ if (!approved)
54985
+ return JSON.stringify({ status: "rejected", reason: comment || "User denied project deletion" });
54382
54986
  }
54987
+ ctx.projectService.deleteProject(projectId);
54988
+ return JSON.stringify({ status: "success", deleted: projectId });
54989
+ } catch (error) {
54990
+ return JSON.stringify({ status: "error", error: String(error) });
54383
54991
  }
54992
+ }
54993
+ }
54994
+ ] : [],
54995
+ ...ctx.getProjectStats ? [
54996
+ {
54997
+ name: "project_stats",
54998
+ description: "Get task and requirement statistics for a project: counts by status (completed, in_progress, blocked, etc.).",
54999
+ inputSchema: {
55000
+ type: "object",
55001
+ properties: {
55002
+ project_id: { type: "string", description: "The project ID" }
55003
+ },
55004
+ required: ["project_id"]
54384
55005
  },
54385
55006
  async execute(args) {
54386
55007
  try {
54387
- const info4 = await ctx.getProjectInfo(args["project_id"]);
54388
- if (!info4)
54389
- return JSON.stringify({ status: "error", error: "No project found" });
54390
- return JSON.stringify({
54391
- status: "success",
54392
- project: {
54393
- id: info4.id,
54394
- name: info4.name,
54395
- description: info4.description,
54396
- status: info4.status,
54397
- repositories: info4.repositories?.map((r) => ({ path: r.localPath, branch: r.defaultBranch })),
54398
- teamCount: info4.teamIds?.length ?? 0,
54399
- governance: info4.governancePolicy?.enabled ? info4.governancePolicy.defaultTier : "none"
54400
- }
54401
- });
55008
+ const stats = await ctx.getProjectStats(args["project_id"]);
55009
+ if (!stats)
55010
+ return JSON.stringify({ status: "error", error: "Project not found" });
55011
+ return JSON.stringify({ status: "success", ...stats });
54402
55012
  } catch (error) {
54403
55013
  return JSON.stringify({ status: "error", error: String(error) });
54404
55014
  }
@@ -57090,7 +57700,6 @@ var init_agent_manager = __esm({
57090
57700
  userNotifier;
57091
57701
  taskService;
57092
57702
  projectService;
57093
- knowledgeService;
57094
57703
  deliverableService;
57095
57704
  webUiBaseUrl;
57096
57705
  semanticSearch;
@@ -57113,49 +57722,6 @@ var init_agent_manager = __esm({
57113
57722
  teamUpdater;
57114
57723
  agentConfigPersister;
57115
57724
  groupChatHandlers;
57116
- buildKnowledgeCallbacks(agentId2, orgId2) {
57117
- if (!this.knowledgeService)
57118
- return {};
57119
- const ks = this.knowledgeService;
57120
- return {
57121
- knowledgeContribute: async (opts) => {
57122
- const scopeId = opts.scope === "org" ? orgId2 : opts.scope === "project" ? orgId2 : agentId2;
57123
- const entry = ks.contribute({
57124
- scope: opts.scope,
57125
- scopeId,
57126
- category: opts.category,
57127
- title: opts.title,
57128
- content: opts.content,
57129
- source: agentId2,
57130
- importance: opts.importance,
57131
- tags: opts.tags?.split(",").map((t) => t.trim()).filter(Boolean),
57132
- supersedes: opts.supersedes
57133
- });
57134
- return { id: entry.id, status: entry.status, filePath: ks.getEntryFilePath?.(entry.id) };
57135
- },
57136
- knowledgeSearch: async (query2, scope, category, limit) => {
57137
- const results = ks.search({ query: query2, scope, category, limit });
57138
- return results.map((e) => ({
57139
- id: e.id,
57140
- title: e.title,
57141
- category: e.category,
57142
- content: e.content,
57143
- importance: e.importance,
57144
- filePath: ks.getEntryFilePath?.(e.id)
57145
- }));
57146
- },
57147
- knowledgeBrowse: async (category, scope) => {
57148
- return ks.browse({
57149
- scope: scope ?? "project",
57150
- scopeId: orgId2,
57151
- category
57152
- });
57153
- },
57154
- knowledgeFlagOutdated: async (id, reason) => {
57155
- ks.flagOutdated(id, reason);
57156
- }
57157
- };
57158
- }
57159
57725
  buildDeliverableCallbacks(agentId2, projectId) {
57160
57726
  if (!this.deliverableService)
57161
57727
  return {};
@@ -57405,9 +57971,6 @@ Priority: ${delegation.priority}`, envelope.from, { name: envelope.from, role: "
57405
57971
  setRequirementService(requirementService) {
57406
57972
  this.requirementService = requirementService;
57407
57973
  }
57408
- setKnowledgeService(knowledgeService) {
57409
- this.knowledgeService = knowledgeService;
57410
- }
57411
57974
  setDeliverableService(deliverableService) {
57412
57975
  this.deliverableService = deliverableService;
57413
57976
  }
@@ -57952,13 +58515,59 @@ Known issues: ${knownIssues}` : ""}`
57952
58515
  }
57953
58516
  if (this.projectService) {
57954
58517
  const ah = this.approvalHandler;
58518
+ const ps = this.projectService;
58519
+ const ts = this.taskService;
58520
+ const rs = this.requirementService;
57955
58521
  for (const tool of createProjectTools({
57956
58522
  agentId: id,
57957
58523
  orgId: config.orgId,
57958
58524
  webUiBaseUrl: this.webUiBaseUrl,
57959
- projectService: this.projectService,
58525
+ projectService: ps,
57960
58526
  requestApproval: ah ? (req) => ah(id, req) : void 0,
57961
- ...this.buildKnowledgeCallbacks(id, config.orgId),
58527
+ getProjectInfo: async (projectId) => {
58528
+ let resolvedId = projectId;
58529
+ if (!resolvedId && config.teamId) {
58530
+ resolvedId = ps.listProjects(config.orgId).find((p) => p.teamIds.includes(config.teamId))?.id;
58531
+ }
58532
+ if (!resolvedId)
58533
+ return null;
58534
+ const project = ps.getProject(resolvedId);
58535
+ return project ?? null;
58536
+ },
58537
+ getProjectStats: ts ? async (projectId) => {
58538
+ const tasks = ts.listTasks({ orgId: config.orgId, projectId });
58539
+ const reqs = rs?.listRequirements?.({ projectId }) ?? [];
58540
+ const stats = {
58541
+ totalTasks: tasks.length,
58542
+ completed: 0,
58543
+ inProgress: 0,
58544
+ inReview: 0,
58545
+ blocked: 0,
58546
+ pending: 0,
58547
+ failed: 0,
58548
+ totalRequirements: reqs.length,
58549
+ completedRequirements: 0
58550
+ };
58551
+ for (const t of tasks) {
58552
+ if (t.status === "completed" || t.status === "accepted")
58553
+ stats.completed++;
58554
+ else if (t.status === "in_progress")
58555
+ stats.inProgress++;
58556
+ else if (t.status === "review" || t.status === "revision")
58557
+ stats.inReview++;
58558
+ else if (t.status === "blocked")
58559
+ stats.blocked++;
58560
+ else if (t.status === "failed")
58561
+ stats.failed++;
58562
+ else
58563
+ stats.pending++;
58564
+ }
58565
+ for (const r of reqs) {
58566
+ if (r.status === "completed")
58567
+ stats.completedRequirements++;
58568
+ }
58569
+ return stats;
58570
+ } : void 0,
57962
58571
  ...this.buildDeliverableCallbacks(id, config.orgId)
57963
58572
  })) {
57964
58573
  agent.registerTool(tool);
@@ -58014,6 +58623,7 @@ Known issues: ${knownIssues}` : ""}`
58014
58623
  }
58015
58624
  }
58016
58625
  {
58626
+ const pkgAh = this.approvalHandler;
58017
58627
  const packageTools = createPackageTools({
58018
58628
  installArtifact: () => this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
58019
58629
  listArtifacts: () => this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
@@ -58036,7 +58646,8 @@ Known issues: ${knownIssues}` : ""}`
58036
58646
  category: t.category ?? "general"
58037
58647
  })) : void 0,
58038
58648
  searchHub: () => this.hubClient ? (opts) => this.hubClient.search(opts) : void 0,
58039
- downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0
58649
+ downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0,
58650
+ requestApproval: pkgAh ? (req) => pkgAh(id, req) : void 0
58040
58651
  });
58041
58652
  for (const tool of packageTools)
58042
58653
  agent.registerTool(tool);
@@ -58081,6 +58692,7 @@ Known issues: ${knownIssues}` : ""}`
58081
58692
  if (this.activityCallbacks) {
58082
58693
  agent.setActivityCallbacks(this.activityCallbacks);
58083
58694
  }
58695
+ agent.setBrowserCloseTabsHelper((sessionId) => this.browserSessionManager.consumeCloseTabsReminder(id, sessionId));
58084
58696
  this.forwardAgentEvents(agent);
58085
58697
  if (config.teamId) {
58086
58698
  agent.setTeamDataDir(join12(homedir6(), ".markus", "teams", config.teamId));
@@ -58563,13 +59175,59 @@ Known issues: ${knownIssues}` : ""}`
58563
59175
  }
58564
59176
  if (this.projectService) {
58565
59177
  const ah2 = this.approvalHandler;
59178
+ const ps2 = this.projectService;
59179
+ const ts2 = this.taskService;
59180
+ const rs2 = this.requirementService;
58566
59181
  for (const tool of createProjectTools({
58567
59182
  agentId: id,
58568
59183
  orgId: config.orgId,
58569
59184
  webUiBaseUrl: this.webUiBaseUrl,
58570
- projectService: this.projectService,
59185
+ projectService: ps2,
58571
59186
  requestApproval: ah2 ? (req) => ah2(id, req) : void 0,
58572
- ...this.buildKnowledgeCallbacks(id, config.orgId),
59187
+ getProjectInfo: async (projectId) => {
59188
+ let resolvedId2 = projectId;
59189
+ if (!resolvedId2 && config.teamId) {
59190
+ resolvedId2 = ps2.listProjects(config.orgId).find((p) => p.teamIds.includes(config.teamId))?.id;
59191
+ }
59192
+ if (!resolvedId2)
59193
+ return null;
59194
+ const project = ps2.getProject(resolvedId2);
59195
+ return project ?? null;
59196
+ },
59197
+ getProjectStats: ts2 ? async (projectId) => {
59198
+ const tasks = ts2.listTasks({ orgId: config.orgId, projectId });
59199
+ const reqs = rs2?.listRequirements?.({ projectId }) ?? [];
59200
+ const stats = {
59201
+ totalTasks: tasks.length,
59202
+ completed: 0,
59203
+ inProgress: 0,
59204
+ inReview: 0,
59205
+ blocked: 0,
59206
+ pending: 0,
59207
+ failed: 0,
59208
+ totalRequirements: reqs.length,
59209
+ completedRequirements: 0
59210
+ };
59211
+ for (const t of tasks) {
59212
+ if (t.status === "completed" || t.status === "accepted")
59213
+ stats.completed++;
59214
+ else if (t.status === "in_progress")
59215
+ stats.inProgress++;
59216
+ else if (t.status === "review" || t.status === "revision")
59217
+ stats.inReview++;
59218
+ else if (t.status === "blocked")
59219
+ stats.blocked++;
59220
+ else if (t.status === "failed")
59221
+ stats.failed++;
59222
+ else
59223
+ stats.pending++;
59224
+ }
59225
+ for (const r of reqs) {
59226
+ if (r.status === "completed")
59227
+ stats.completedRequirements++;
59228
+ }
59229
+ return stats;
59230
+ } : void 0,
58573
59231
  ...this.buildDeliverableCallbacks(id, config.orgId)
58574
59232
  })) {
58575
59233
  agent.registerTool(tool);
@@ -58624,6 +59282,7 @@ Known issues: ${knownIssues}` : ""}`
58624
59282
  agent.registerTool(tool);
58625
59283
  }
58626
59284
  {
59285
+ const pkgAh2 = this.approvalHandler;
58627
59286
  const packageTools = createPackageTools({
58628
59287
  installArtifact: () => this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
58629
59288
  listArtifacts: () => this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
@@ -58646,7 +59305,8 @@ Known issues: ${knownIssues}` : ""}`
58646
59305
  category: t.category ?? "general"
58647
59306
  })) : void 0,
58648
59307
  searchHub: () => this.hubClient ? (opts) => this.hubClient.search(opts) : void 0,
58649
- downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0
59308
+ downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0,
59309
+ requestApproval: pkgAh2 ? (req) => pkgAh2(id, req) : void 0
58650
59310
  });
58651
59311
  for (const tool of packageTools)
58652
59312
  agent.registerTool(tool);
@@ -58666,6 +59326,7 @@ Known issues: ${knownIssues}` : ""}`
58666
59326
  if (this.activityCallbacks) {
58667
59327
  agent.setActivityCallbacks(this.activityCallbacks);
58668
59328
  }
59329
+ agent.setBrowserCloseTabsHelper((sessionId) => this.browserSessionManager.consumeCloseTabsReminder(id, sessionId));
58669
59330
  this.forwardAgentEvents(agent);
58670
59331
  if (config.teamId) {
58671
59332
  agent.setTeamDataDir(join12(homedir6(), ".markus", "teams", config.teamId));
@@ -63672,7 +64333,7 @@ var init_tool_profiles = __esm({
63672
64333
  "group:agent": ["spawn_subagent", "spawn_subagents"],
63673
64334
  "group:tasks": ["task_create", "task_list", "task_get", "task_update", "task_note", "task_assign", "task_submit_review", "task_comment", "subtask_create", "subtask_complete", "subtask_list"],
63674
64335
  "group:requirements": ["requirement_propose", "requirement_list", "requirement_get", "requirement_update", "requirement_update_status", "requirement_resubmit", "requirement_comment"],
63675
- "group:project": ["list_projects", "get_project", "create_project", "update_project", "project_info", "deliverable_create", "deliverable_search", "deliverable_list", "deliverable_update"],
64336
+ "group:project": ["list_projects", "get_project", "create_project", "update_project", "delete_project", "project_info", "project_stats", "deliverable_create", "deliverable_search", "deliverable_list", "deliverable_update"],
63676
64337
  "group:manager": ["team_list", "team_status", "delegate_message", "team_update", "agent_update", "task_check_duplicates", "task_cleanup_duplicates", "task_board_health"],
63677
64338
  "group:packages": ["package_list", "package_install", "hub_search", "hub_install"]
63678
64339
  };
@@ -65857,11 +66518,15 @@ var init_org_service = __esm({
65857
66518
  pendingAgentStartIds = [];
65858
66519
  roleLoader;
65859
66520
  storage;
66521
+ deliverableService;
65860
66522
  constructor(agentManager, roleLoader, storage) {
65861
66523
  this.agentManager = agentManager;
65862
66524
  this.roleLoader = roleLoader ?? new RoleLoader();
65863
66525
  this.storage = storage;
65864
66526
  }
66527
+ setDeliverableService(svc) {
66528
+ this.deliverableService = svc;
66529
+ }
65865
66530
  // ─── Human User Management ───
65866
66531
  addHumanUser(orgId2, name, role, opts) {
65867
66532
  const org = this.orgs.get(orgId2);
@@ -66430,6 +67095,17 @@ var init_org_service = __esm({
66430
67095
  if (this.isProtectedAgent(agentId2)) {
66431
67096
  throw new Error("The Secretary agent is a protected system agent and cannot be deleted.");
66432
67097
  }
67098
+ if (this.deliverableService) {
67099
+ const agentDir = join19(this.agentManager.getDataDir(), agentId2);
67100
+ const sharedDir = this.agentManager.getSharedDataDir();
67101
+ if (sharedDir) {
67102
+ try {
67103
+ await this.deliverableService.migrateAgentFiles(agentId2, agentDir, sharedDir);
67104
+ } catch (err) {
67105
+ log53.warn("Failed to migrate deliverable files before agent removal", { agentId: agentId2, error: String(err) });
67106
+ }
67107
+ }
67108
+ }
66433
67109
  const agentInfo = this.agentManager.listAgents().find((a) => a.id === agentId2);
66434
67110
  await this.agentManager.removeAgent(agentId2, { purgeFiles: opts?.purgeFiles });
66435
67111
  for (const org of this.orgs.values()) {
@@ -82676,7 +83352,7 @@ ${cleanText}`,
82676
83352
  });
82677
83353
  return;
82678
83354
  }
82679
- if (path.match(/^\/api\/agents\/[^/]+\/(start|stop|daily-report|a2a|message)$/) && req.method === "POST") {
83355
+ if (path.match(/^\/api\/agents\/[^/]+\/(start|stop|pause|resume|cancel-processing|daily-report|a2a|message)$/) && req.method === "POST") {
82680
83356
  const authUser = await this.requireAuth(req, res);
82681
83357
  if (!authUser)
82682
83358
  return;
@@ -82695,6 +83371,29 @@ ${cleanText}`,
82695
83371
  this.json(res, 200, { status: "stopped" });
82696
83372
  return;
82697
83373
  }
83374
+ if (action === "pause") {
83375
+ const body = await this.readBody(req);
83376
+ const reason = body["reason"];
83377
+ const agent = this.orgService.getAgentManager().getAgent(agentId2);
83378
+ agent.pause(reason);
83379
+ this.ws.broadcastAgentUpdate(agentId2, "paused");
83380
+ this.json(res, 200, { status: "paused" });
83381
+ return;
83382
+ }
83383
+ if (action === "resume") {
83384
+ const agent = this.orgService.getAgentManager().getAgent(agentId2);
83385
+ agent.resume();
83386
+ const newStatus = agent.getState().status;
83387
+ this.ws.broadcastAgentUpdate(agentId2, newStatus);
83388
+ this.json(res, 200, { status: newStatus });
83389
+ return;
83390
+ }
83391
+ if (action === "cancel-processing") {
83392
+ const agent = this.orgService.getAgentManager().getAgent(agentId2);
83393
+ agent.cancelActiveStream();
83394
+ this.json(res, 200, { status: "cancelled" });
83395
+ return;
83396
+ }
82698
83397
  if (action === "daily-report") {
82699
83398
  const agent = this.orgService.getAgentManager().getAgent(agentId2);
82700
83399
  const report = await agent.generateDailyReport();
@@ -83365,6 +84064,16 @@ ${cleanText}`,
83365
84064
  this.json(res, 200, { results, total });
83366
84065
  return;
83367
84066
  }
84067
+ if (path === "/api/deliverables/health" && req.method === "GET") {
84068
+ if (!this.deliverableService) {
84069
+ this.json(res, 503, { error: "Deliverable service not available" });
84070
+ return;
84071
+ }
84072
+ const agentId2 = url.searchParams.get("agentId") ?? void 0;
84073
+ const missingIds = this.deliverableService.checkFileHealth(agentId2);
84074
+ this.json(res, 200, { missingFiles: missingIds });
84075
+ return;
84076
+ }
83368
84077
  if (path === "/api/deliverables" && req.method === "POST") {
83369
84078
  const authUser = await this.requireAuth(req, res);
83370
84079
  if (!authUser)
@@ -85462,12 +86171,12 @@ EXPLANATION_END`;
85462
86171
  const installedSkills = new Map((this.skillRegistry?.list() ?? []).map((s2) => [s2.name, s2]));
85463
86172
  const rawManifests = /* @__PURE__ */ new Map();
85464
86173
  try {
85465
- const { readdirSync: readdirSync14, readFileSync: readFileSync28, existsSync: existsSync38 } = await import("node:fs");
86174
+ const { readdirSync: readdirSync14, readFileSync: readFileSync28, existsSync: existsSync39 } = await import("node:fs");
85466
86175
  for (const entry of readdirSync14(builtinDir, { withFileTypes: true })) {
85467
86176
  if (!entry.isDirectory())
85468
86177
  continue;
85469
86178
  const sjPath = resolve15(builtinDir, entry.name, "skill.json");
85470
- if (existsSync38(sjPath)) {
86179
+ if (existsSync39(sjPath)) {
85471
86180
  try {
85472
86181
  rawManifests.set(entry.name, JSON.parse(readFileSync28(sjPath, "utf-8")));
85473
86182
  } catch {
@@ -87378,7 +88087,12 @@ data: ${JSON.stringify({ error: msg })}
87378
88087
  const mask = (key2) => key2 ? "***" + key2.slice(-4) : "";
87379
88088
  this.json(res, 200, {
87380
88089
  serper: { configured: !!search.serperApiKey || !!process.env["SERPER_API_KEY"], preview: mask(search.serperApiKey || process.env["SERPER_API_KEY"]) },
88090
+ tavily: { configured: !!search.tavilyApiKey || !!process.env["TAVILY_API_KEY"], preview: mask(search.tavilyApiKey || process.env["TAVILY_API_KEY"]) },
88091
+ bing: { configured: !!search.bingApiKey || !!process.env["BING_SEARCH_API_KEY"], preview: mask(search.bingApiKey || process.env["BING_SEARCH_API_KEY"]) },
88092
+ google: { configured: !!(search.googleSearchApiKey && search.googleSearchCx) || !!(process.env["GOOGLE_SEARCH_API_KEY"] && process.env["GOOGLE_SEARCH_CX"]), preview: mask(search.googleSearchApiKey || process.env["GOOGLE_SEARCH_API_KEY"]) },
88093
+ serpapi: { configured: !!search.serpApiKey || !!process.env["SERPAPI_API_KEY"], preview: mask(search.serpApiKey || process.env["SERPAPI_API_KEY"]) },
87381
88094
  brave: { configured: !!search.braveApiKey || !!process.env["BRAVE_SEARCH_API_KEY"], preview: mask(search.braveApiKey || process.env["BRAVE_SEARCH_API_KEY"]) },
88095
+ exa: { configured: !!search.exaApiKey || !!process.env["EXA_API_KEY"], preview: mask(search.exaApiKey || process.env["EXA_API_KEY"]) },
87382
88096
  bocha: { configured: !!search.bochaApiKey || !!process.env["BOCHA_API_KEY"], preview: mask(search.bochaApiKey || process.env["BOCHA_API_KEY"]) }
87383
88097
  });
87384
88098
  return;
@@ -87396,6 +88110,41 @@ data: ${JSON.stringify({ error: msg })}
87396
88110
  else
87397
88111
  delete process.env["SERPER_API_KEY"];
87398
88112
  }
88113
+ if (typeof body["tavilyApiKey"] === "string") {
88114
+ updates.tavilyApiKey = body["tavilyApiKey"] || void 0;
88115
+ if (body["tavilyApiKey"])
88116
+ process.env["TAVILY_API_KEY"] = body["tavilyApiKey"];
88117
+ else
88118
+ delete process.env["TAVILY_API_KEY"];
88119
+ }
88120
+ if (typeof body["bingApiKey"] === "string") {
88121
+ updates.bingApiKey = body["bingApiKey"] || void 0;
88122
+ if (body["bingApiKey"])
88123
+ process.env["BING_SEARCH_API_KEY"] = body["bingApiKey"];
88124
+ else
88125
+ delete process.env["BING_SEARCH_API_KEY"];
88126
+ }
88127
+ if (typeof body["googleSearchApiKey"] === "string") {
88128
+ updates.googleSearchApiKey = body["googleSearchApiKey"] || void 0;
88129
+ if (body["googleSearchApiKey"])
88130
+ process.env["GOOGLE_SEARCH_API_KEY"] = body["googleSearchApiKey"];
88131
+ else
88132
+ delete process.env["GOOGLE_SEARCH_API_KEY"];
88133
+ }
88134
+ if (typeof body["googleSearchCx"] === "string") {
88135
+ updates.googleSearchCx = body["googleSearchCx"] || void 0;
88136
+ if (body["googleSearchCx"])
88137
+ process.env["GOOGLE_SEARCH_CX"] = body["googleSearchCx"];
88138
+ else
88139
+ delete process.env["GOOGLE_SEARCH_CX"];
88140
+ }
88141
+ if (typeof body["serpApiKey"] === "string") {
88142
+ updates.serpApiKey = body["serpApiKey"] || void 0;
88143
+ if (body["serpApiKey"])
88144
+ process.env["SERPAPI_API_KEY"] = body["serpApiKey"];
88145
+ else
88146
+ delete process.env["SERPAPI_API_KEY"];
88147
+ }
87399
88148
  if (typeof body["braveApiKey"] === "string") {
87400
88149
  updates.braveApiKey = body["braveApiKey"] || void 0;
87401
88150
  if (body["braveApiKey"])
@@ -87403,6 +88152,13 @@ data: ${JSON.stringify({ error: msg })}
87403
88152
  else
87404
88153
  delete process.env["BRAVE_SEARCH_API_KEY"];
87405
88154
  }
88155
+ if (typeof body["exaApiKey"] === "string") {
88156
+ updates.exaApiKey = body["exaApiKey"] || void 0;
88157
+ if (body["exaApiKey"])
88158
+ process.env["EXA_API_KEY"] = body["exaApiKey"];
88159
+ else
88160
+ delete process.env["EXA_API_KEY"];
88161
+ }
87406
88162
  if (typeof body["bochaApiKey"] === "string") {
87407
88163
  updates.bochaApiKey = body["bochaApiKey"] || void 0;
87408
88164
  if (body["bochaApiKey"])
@@ -87430,7 +88186,12 @@ data: ${JSON.stringify({ error: msg })}
87430
88186
  const mask = (key2) => key2 ? "***" + key2.slice(-4) : "";
87431
88187
  this.json(res, 200, {
87432
88188
  serper: { configured: !!search.serperApiKey || !!process.env["SERPER_API_KEY"], preview: mask(search.serperApiKey || process.env["SERPER_API_KEY"]) },
88189
+ tavily: { configured: !!search.tavilyApiKey || !!process.env["TAVILY_API_KEY"], preview: mask(search.tavilyApiKey || process.env["TAVILY_API_KEY"]) },
88190
+ bing: { configured: !!search.bingApiKey || !!process.env["BING_SEARCH_API_KEY"], preview: mask(search.bingApiKey || process.env["BING_SEARCH_API_KEY"]) },
88191
+ google: { configured: !!(search.googleSearchApiKey && search.googleSearchCx) || !!(process.env["GOOGLE_SEARCH_API_KEY"] && process.env["GOOGLE_SEARCH_CX"]), preview: mask(search.googleSearchApiKey || process.env["GOOGLE_SEARCH_API_KEY"]) },
88192
+ serpapi: { configured: !!search.serpApiKey || !!process.env["SERPAPI_API_KEY"], preview: mask(search.serpApiKey || process.env["SERPAPI_API_KEY"]) },
87433
88193
  brave: { configured: !!search.braveApiKey || !!process.env["BRAVE_SEARCH_API_KEY"], preview: mask(search.braveApiKey || process.env["BRAVE_SEARCH_API_KEY"]) },
88194
+ exa: { configured: !!search.exaApiKey || !!process.env["EXA_API_KEY"], preview: mask(search.exaApiKey || process.env["EXA_API_KEY"]) },
87434
88195
  bocha: { configured: !!search.bochaApiKey || !!process.env["BOCHA_API_KEY"], preview: mask(search.bochaApiKey || process.env["BOCHA_API_KEY"]) }
87435
88196
  });
87436
88197
  return;
@@ -88696,7 +89457,7 @@ data: ${JSON.stringify({ error: msg })}
88696
89457
  }
88697
89458
  try {
88698
89459
  const { resolve: resolve22, extname: extname2 } = await import("node:path");
88699
- const { existsSync: existsSync38, statSync: statSync7 } = await import("node:fs");
89460
+ const { existsSync: existsSync39, statSync: statSync7 } = await import("node:fs");
88700
89461
  const { homedir: homedir22 } = await import("node:os");
88701
89462
  const home = homedir22();
88702
89463
  const results = {};
@@ -88708,7 +89469,7 @@ data: ${JSON.stringify({ error: msg })}
88708
89469
  try {
88709
89470
  const expanded = p.startsWith("~/") ? resolve22(home, p.slice(2)) : p === "~" ? home : p;
88710
89471
  const resolved = resolve22(expanded);
88711
- if (!existsSync38(resolved)) {
89472
+ if (!existsSync39(resolved)) {
88712
89473
  results[p] = { exists: false, isFile: false, type: "unknown" };
88713
89474
  continue;
88714
89475
  }
@@ -88745,21 +89506,21 @@ data: ${JSON.stringify({ error: msg })}
88745
89506
  }
88746
89507
  try {
88747
89508
  const { resolve: resolve22, extname: extname2 } = await import("node:path");
88748
- const { readFileSync: readFileSync28, existsSync: existsSync38, statSync: statSync7 } = await import("node:fs");
89509
+ const { readFileSync: readFileSync28, existsSync: existsSync39, statSync: statSync7 } = await import("node:fs");
88749
89510
  const { homedir: homedir22 } = await import("node:os");
88750
89511
  const home = homedir22();
88751
89512
  const expanded = filePath.startsWith("~/") ? resolve22(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
88752
89513
  const resolved = resolve22(expanded);
88753
- if (!existsSync38(resolved)) {
89514
+ if (!existsSync39(resolved)) {
88754
89515
  this.json(res, 404, { error: "File not found" });
88755
89516
  return;
88756
89517
  }
88757
89518
  const stat = statSync7(resolved);
88758
89519
  if (stat.isDirectory()) {
88759
89520
  const { readdirSync: readdirSync14 } = await import("node:fs");
88760
- const { join: join31, extname: extDir } = await import("node:path");
89521
+ const { join: join32, extname: extDir } = await import("node:path");
88761
89522
  const entries2 = readdirSync14(resolved, { withFileTypes: true }).filter((e) => !e.name.startsWith(".")).map((e) => {
88762
- const full = join31(resolved, e.name);
89523
+ const full = join32(resolved, e.name);
88763
89524
  const isDir = e.isDirectory();
88764
89525
  let size;
88765
89526
  try {
@@ -88830,12 +89591,12 @@ data: ${JSON.stringify({ error: msg })}
88830
89591
  }
88831
89592
  try {
88832
89593
  const { resolve: resolve22, extname: extname2 } = await import("node:path");
88833
- const { readFileSync: readFileSync28, existsSync: existsSync38, statSync: statSync7 } = await import("node:fs");
89594
+ const { readFileSync: readFileSync28, existsSync: existsSync39, statSync: statSync7 } = await import("node:fs");
88834
89595
  const { homedir: homedir22 } = await import("node:os");
88835
89596
  const home = homedir22();
88836
89597
  const expanded = filePath.startsWith("~/") ? resolve22(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
88837
89598
  const resolved = resolve22(expanded);
88838
- if (!existsSync38(resolved) || !statSync7(resolved).isFile()) {
89599
+ if (!existsSync39(resolved) || !statSync7(resolved).isFile()) {
88839
89600
  this.json(res, 404, { error: "Image not found" });
88840
89601
  return;
88841
89602
  }
@@ -88881,13 +89642,13 @@ data: ${JSON.stringify({ error: msg })}
88881
89642
  }
88882
89643
  try {
88883
89644
  const { resolve: resolve22, dirname: dirname12 } = await import("node:path");
88884
- const { existsSync: existsSync38, statSync: statSync7 } = await import("node:fs");
89645
+ const { existsSync: existsSync39, statSync: statSync7 } = await import("node:fs");
88885
89646
  const { exec: exec2 } = await import("node:child_process");
88886
89647
  const { homedir: homedir22 } = await import("node:os");
88887
89648
  const home = homedir22();
88888
89649
  const expanded = filePath.startsWith("~/") ? resolve22(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
88889
89650
  const resolved = resolve22(expanded);
88890
- if (!existsSync38(resolved)) {
89651
+ if (!existsSync39(resolved)) {
88891
89652
  this.json(res, 404, { error: "Path not found" });
88892
89653
  return;
88893
89654
  }
@@ -89612,7 +90373,7 @@ data: ${JSON.stringify({ error: msg })}
89612
90373
  exact("/api/agents", "GET", "POST"),
89613
90374
  exact("/api/agents/role-updates", "GET"),
89614
90375
  regex(/^\/api\/agents\/[^/]+\/sessions$/, "GET"),
89615
- regex(/^\/api\/agents\/[^/]+\/(start|stop|daily-report|a2a|message)$/, "POST"),
90376
+ regex(/^\/api\/agents\/[^/]+\/(start|stop|pause|resume|cancel-processing|daily-report|a2a|message)$/, "POST"),
89616
90377
  regex(/^\/api\/agents\/[^/]+$/, "GET", "DELETE"),
89617
90378
  regex(/^\/api\/agents\/[^/]+\/mind$/, "GET"),
89618
90379
  regex(/^\/api\/agents\/[^/]+\/mailbox$/, "GET"),
@@ -89695,6 +90456,7 @@ data: ${JSON.stringify({ error: msg })}
89695
90456
  exact("/api/execution-logs", "GET"),
89696
90457
  // ── Deliverables / Knowledge ─────────────────────────────────────────
89697
90458
  exact("/api/deliverables", "GET", "POST"),
90459
+ exact("/api/deliverables/health", "GET"),
89698
90460
  regex(/^\/api\/deliverables\/[^/]+$/, "GET", "PUT", "DELETE"),
89699
90461
  exact("/api/knowledge", "POST"),
89700
90462
  exact("/api/knowledge/search", "GET"),
@@ -92057,6 +92819,11 @@ var init_file_knowledge_store = __esm({
92057
92819
  });
92058
92820
 
92059
92821
  // ../org-manager/dist/deliverable-service.js
92822
+ import { existsSync as existsSync30, cpSync as cpSync3, mkdirSync as mkdirSync20 } from "node:fs";
92823
+ import { join as join25, basename } from "node:path";
92824
+ function isUrl(s2) {
92825
+ return /^https?:\/\//i.test(s2);
92826
+ }
92060
92827
  var log68, DeliverableService;
92061
92828
  var init_deliverable_service = __esm({
92062
92829
  "../org-manager/dist/deliverable-service.js"() {
@@ -92103,6 +92870,8 @@ var init_deliverable_service = __esm({
92103
92870
  patch.diffStats = opts.diffStats;
92104
92871
  if (opts.testResults !== void 0)
92105
92872
  patch.testResults = opts.testResults;
92873
+ if (opts.taskId && !existing.taskId)
92874
+ patch.taskId = opts.taskId;
92106
92875
  const updated = await this.update(existing.id, patch);
92107
92876
  log68.info("Deliverable upserted (updated existing)", { id: existing.id, reference: ref });
92108
92877
  this.ws?.broadcastDeliverableUpdate(existing.id, "updated", {
@@ -92223,6 +92992,40 @@ var init_deliverable_service = __esm({
92223
92992
  const d = this.cache.get(id);
92224
92993
  if (!d)
92225
92994
  return void 0;
92995
+ const arrEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
92996
+ const changed = [];
92997
+ if (data.type !== void 0 && data.type !== d.type)
92998
+ changed.push("type");
92999
+ if (data.title !== void 0 && data.title !== d.title)
93000
+ changed.push("title");
93001
+ if (data.summary !== void 0 && data.summary !== d.summary)
93002
+ changed.push("summary");
93003
+ if (data.reference !== void 0 && data.reference !== d.reference)
93004
+ changed.push("reference");
93005
+ if (data.format !== void 0 && data.format !== d.format)
93006
+ changed.push("format");
93007
+ if (data.tags !== void 0 && !arrEq(data.tags, d.tags))
93008
+ changed.push("tags");
93009
+ if (data.status !== void 0 && data.status !== d.status)
93010
+ changed.push("status");
93011
+ if (data.taskId !== void 0 && data.taskId !== d.taskId)
93012
+ changed.push("taskId");
93013
+ if (data.agentId !== void 0 && data.agentId !== d.agentId)
93014
+ changed.push("agentId");
93015
+ if (data.projectId !== void 0 && data.projectId !== d.projectId)
93016
+ changed.push("projectId");
93017
+ if (data.artifactType !== void 0 && data.artifactType !== d.artifactType)
93018
+ changed.push("artifactType");
93019
+ if (data.artifactData !== void 0 && !arrEq(data.artifactData, d.artifactData))
93020
+ changed.push("artifactData");
93021
+ if (data.diffStats !== void 0 && !arrEq(data.diffStats, d.diffStats))
93022
+ changed.push("diffStats");
93023
+ if (data.testResults !== void 0 && !arrEq(data.testResults, d.testResults))
93024
+ changed.push("testResults");
93025
+ if (changed.length === 0) {
93026
+ log68.debug("Deliverable update skipped (no-op)", { id });
93027
+ return d;
93028
+ }
92226
93029
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
92227
93030
  if (data.type !== void 0)
92228
93031
  d.type = data.type;
@@ -92238,6 +93041,12 @@ var init_deliverable_service = __esm({
92238
93041
  d.tags = data.tags;
92239
93042
  if (data.status !== void 0)
92240
93043
  d.status = data.status;
93044
+ if (data.taskId !== void 0)
93045
+ d.taskId = data.taskId;
93046
+ if (data.agentId !== void 0)
93047
+ d.agentId = data.agentId;
93048
+ if (data.projectId !== void 0)
93049
+ d.projectId = data.projectId;
92241
93050
  if (data.artifactType !== void 0)
92242
93051
  d.artifactType = data.artifactType;
92243
93052
  if (data.artifactData !== void 0)
@@ -92248,7 +93057,7 @@ var init_deliverable_service = __esm({
92248
93057
  d.testResults = data.testResults;
92249
93058
  d.updatedAt = now3;
92250
93059
  await this.repo?.update(id, data);
92251
- log68.info("Deliverable updated", { id, fields: Object.keys(data) });
93060
+ log68.info("Deliverable updated", { id, fields: changed });
92252
93061
  this.ws?.broadcastDeliverableUpdate(id, "updated", {
92253
93062
  type: d.type,
92254
93063
  title: d.title,
@@ -92331,6 +93140,58 @@ var init_deliverable_service = __esm({
92331
93140
  }
92332
93141
  return cleaned;
92333
93142
  }
93143
+ /**
93144
+ * Migrate deliverable files from an agent's workspace to the shared directory
93145
+ * before the agent is deleted. Updates each deliverable's reference to point
93146
+ * at the new shared location.
93147
+ */
93148
+ async migrateAgentFiles(agentId2, agentDir, sharedDataDir) {
93149
+ const deliverables = this.findByAgent(agentId2);
93150
+ if (deliverables.length === 0)
93151
+ return 0;
93152
+ const sharedDeliverables = join25(sharedDataDir, "deliverables");
93153
+ let migrated = 0;
93154
+ for (const d of deliverables) {
93155
+ if (!d.reference || isUrl(d.reference))
93156
+ continue;
93157
+ if (!d.reference.startsWith(agentDir + "/") && d.reference !== agentDir)
93158
+ continue;
93159
+ if (!existsSync30(d.reference))
93160
+ continue;
93161
+ try {
93162
+ const destDir = join25(sharedDeliverables, d.id);
93163
+ mkdirSync20(destDir, { recursive: true });
93164
+ const fileName = basename(d.reference);
93165
+ const destPath = join25(destDir, fileName);
93166
+ cpSync3(d.reference, destPath, { recursive: true });
93167
+ await this.update(d.id, { reference: destPath });
93168
+ migrated++;
93169
+ log68.info("Deliverable file migrated to shared", { id: d.id, from: d.reference, to: destPath });
93170
+ } catch (err) {
93171
+ log68.warn("Failed to migrate deliverable file", { id: d.id, reference: d.reference, error: String(err) });
93172
+ }
93173
+ }
93174
+ if (migrated > 0) {
93175
+ log68.info("Migrated agent deliverable files to shared directory", { agentId: agentId2, migrated, total: deliverables.length });
93176
+ }
93177
+ return migrated;
93178
+ }
93179
+ /**
93180
+ * Check file health for deliverables: returns IDs of deliverables whose
93181
+ * referenced files no longer exist on disk.
93182
+ */
93183
+ checkFileHealth(agentId2) {
93184
+ const deliverables = agentId2 ? this.findByAgent(agentId2) : this.getAll();
93185
+ const missing = [];
93186
+ for (const d of deliverables) {
93187
+ if (!d.reference || isUrl(d.reference))
93188
+ continue;
93189
+ if (!existsSync30(d.reference)) {
93190
+ missing.push(d.id);
93191
+ }
93192
+ }
93193
+ return missing;
93194
+ }
92334
93195
  /**
92335
93196
  * One-time migration: scan existing tasks and create Deliverable entries
92336
93197
  * for any task.deliverables that don't yet have a corresponding row.
@@ -93124,7 +93985,7 @@ var init_scheduled_task_runner = __esm({
93124
93985
  // ../storage/dist/sqlite-storage.js
93125
93986
  import { DatabaseSync } from "node:sqlite";
93126
93987
  import { randomUUID } from "node:crypto";
93127
- import { mkdirSync as mkdirSync20 } from "node:fs";
93988
+ import { mkdirSync as mkdirSync21 } from "node:fs";
93128
93989
  import { dirname as dirname8 } from "node:path";
93129
93990
  function generateId2(prefix = "") {
93130
93991
  const uuid = randomUUID().replace(/-/g, "").slice(0, 16);
@@ -93145,7 +94006,7 @@ function toDate(v) {
93145
94006
  function openSqlite(dbPath) {
93146
94007
  if (_db)
93147
94008
  return _db;
93148
- mkdirSync20(dirname8(dbPath), { recursive: true });
94009
+ mkdirSync21(dirname8(dbPath), { recursive: true });
93149
94010
  _db = new DatabaseSync(dbPath);
93150
94011
  _db.exec("PRAGMA journal_mode = WAL");
93151
94012
  _db.exec("PRAGMA foreign_keys = ON");
@@ -95573,6 +96434,18 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
95573
96434
  sets.push("type = ?");
95574
96435
  vals.push(patch.type);
95575
96436
  }
96437
+ if (patch.taskId !== void 0) {
96438
+ sets.push("task_id = ?");
96439
+ vals.push(patch.taskId);
96440
+ }
96441
+ if (patch.agentId !== void 0) {
96442
+ sets.push("agent_id = ?");
96443
+ vals.push(patch.agentId);
96444
+ }
96445
+ if (patch.projectId !== void 0) {
96446
+ sets.push("project_id = ?");
96447
+ vals.push(patch.projectId);
96448
+ }
95576
96449
  if (patch.artifactType !== void 0) {
95577
96450
  sets.push("artifact_type = ?");
95578
96451
  vals.push(patch.artifactType);
@@ -96343,16 +97216,16 @@ var init_dist5 = __esm({
96343
97216
 
96344
97217
  // ../org-manager/dist/storage-bridge.js
96345
97218
  import { homedir as homedir15 } from "node:os";
96346
- import { join as join25 } from "node:path";
97219
+ import { join as join26 } from "node:path";
96347
97220
  function resolveSqlitePath(url) {
96348
97221
  if (url?.startsWith("sqlite:")) {
96349
97222
  let p = url.slice("sqlite:".length);
96350
97223
  if (p.startsWith("~/") || p === "~") {
96351
- p = join25(homedir15(), p.slice(2));
97224
+ p = join26(homedir15(), p.slice(2));
96352
97225
  }
96353
97226
  return p;
96354
97227
  }
96355
- return join25(homedir15(), ".markus", "data.db");
97228
+ return join26(homedir15(), ".markus", "data.db");
96356
97229
  }
96357
97230
  async function initStorage(databaseUrl) {
96358
97231
  const url = databaseUrl ?? process.env["DATABASE_URL"];
@@ -96409,8 +97282,8 @@ var init_storage_bridge = __esm({
96409
97282
  });
96410
97283
 
96411
97284
  // ../org-manager/dist/file-storage-provider.js
96412
- import { mkdirSync as mkdirSync21, writeFileSync as writeFileSync18, unlinkSync as unlinkSync4, existsSync as existsSync30 } from "node:fs";
96413
- import { join as join26, extname } from "node:path";
97285
+ import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, unlinkSync as unlinkSync4, existsSync as existsSync31 } from "node:fs";
97286
+ import { join as join27, extname } from "node:path";
96414
97287
  import { homedir as homedir16 } from "node:os";
96415
97288
  function mimeToExt(mime) {
96416
97289
  const map = {
@@ -96431,27 +97304,27 @@ var init_file_storage_provider = __esm({
96431
97304
  LocalFileStorageProvider = class {
96432
97305
  baseDir;
96433
97306
  constructor(baseDir) {
96434
- this.baseDir = baseDir ?? join26(homedir16(), ".markus", "uploads");
96435
- mkdirSync21(this.baseDir, { recursive: true });
97307
+ this.baseDir = baseDir ?? join27(homedir16(), ".markus", "uploads");
97308
+ mkdirSync22(this.baseDir, { recursive: true });
96436
97309
  }
96437
97310
  async upload(data, opts) {
96438
97311
  const ext = extname(opts.name) || mimeToExt(opts.contentType);
96439
97312
  const key2 = `${generateId("upl")}${ext}`;
96440
- const subDir = opts.prefix ? join26(this.baseDir, opts.prefix) : this.baseDir;
96441
- mkdirSync21(subDir, { recursive: true });
96442
- writeFileSync18(join26(subDir, key2), data);
97313
+ const subDir = opts.prefix ? join27(this.baseDir, opts.prefix) : this.baseDir;
97314
+ mkdirSync22(subDir, { recursive: true });
97315
+ writeFileSync18(join27(subDir, key2), data);
96443
97316
  const urlPath = opts.prefix ? `/api/uploads/${opts.prefix}/${key2}` : `/api/uploads/${key2}`;
96444
97317
  return { url: urlPath, key: opts.prefix ? `${opts.prefix}/${key2}` : key2 };
96445
97318
  }
96446
97319
  async delete(key2) {
96447
- const filePath = join26(this.baseDir, key2);
96448
- if (existsSync30(filePath)) {
97320
+ const filePath = join27(this.baseDir, key2);
97321
+ if (existsSync31(filePath)) {
96449
97322
  unlinkSync4(filePath);
96450
97323
  }
96451
97324
  }
96452
97325
  /** Resolve a storage key to an absolute filesystem path (for serving). */
96453
97326
  resolve(key2) {
96454
- return join26(this.baseDir, key2);
97327
+ return join27(this.baseDir, key2);
96455
97328
  }
96456
97329
  };
96457
97330
  }
@@ -97181,17 +98054,17 @@ var init_dist7 = __esm({
97181
98054
  });
97182
98055
 
97183
98056
  // src/utils/logger.ts
97184
- import { createWriteStream as createWriteStream2, existsSync as existsSync31, mkdirSync as mkdirSync22, appendFileSync as appendFileSync3 } from "node:fs";
97185
- import { join as join27 } from "node:path";
98057
+ import { createWriteStream as createWriteStream2, existsSync as existsSync32, mkdirSync as mkdirSync23, appendFileSync as appendFileSync3 } from "node:fs";
98058
+ import { join as join28 } from "node:path";
97186
98059
  import { homedir as homedir17 } from "node:os";
97187
98060
  function ensureLogDir2() {
97188
- if (!existsSync31(LOG_DIR2)) {
97189
- mkdirSync22(LOG_DIR2, { recursive: true, mode: 493 });
98061
+ if (!existsSync32(LOG_DIR2)) {
98062
+ mkdirSync23(LOG_DIR2, { recursive: true, mode: 493 });
97190
98063
  }
97191
98064
  }
97192
98065
  function getStartupLogPath() {
97193
98066
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
97194
- return join27(LOG_DIR2, `startup-${date}.log`);
98067
+ return join28(LOG_DIR2, `startup-${date}.log`);
97195
98068
  }
97196
98069
  function setSuppressConsole(suppress) {
97197
98070
  _suppressConsole = suppress;
@@ -97246,7 +98119,7 @@ var LOG_DIR2, startupLogStream, startupLogPath, _suppressConsole, LEVEL_PREFIX;
97246
98119
  var init_logger2 = __esm({
97247
98120
  "src/utils/logger.ts"() {
97248
98121
  "use strict";
97249
- LOG_DIR2 = join27(homedir17(), ".markus", "logs");
98122
+ LOG_DIR2 = join28(homedir17(), ".markus", "logs");
97250
98123
  startupLogStream = null;
97251
98124
  startupLogPath = "";
97252
98125
  _suppressConsole = false;
@@ -97303,8 +98176,8 @@ var init_browser = __esm({
97303
98176
 
97304
98177
  // src/utils/startupProgress.ts
97305
98178
  import { homedir as homedir18 } from "node:os";
97306
- import { appendFileSync as appendFileSync4, existsSync as existsSync32, mkdirSync as mkdirSync23 } from "node:fs";
97307
- import { join as join28 } from "node:path";
98179
+ import { appendFileSync as appendFileSync4, existsSync as existsSync33, mkdirSync as mkdirSync24 } from "node:fs";
98180
+ import { join as join29 } from "node:path";
97308
98181
  function clearScreen() {
97309
98182
  return "\x1B[2J\x1B[H";
97310
98183
  }
@@ -97466,8 +98339,8 @@ var init_startupProgress = __esm({
97466
98339
  const line = `${ts} ${msg}
97467
98340
  `;
97468
98341
  try {
97469
- const dir = join28(homedir18(), ".markus", "logs");
97470
- if (!existsSync32(dir)) mkdirSync23(dir, { recursive: true, mode: 493 });
98342
+ const dir = join29(homedir18(), ".markus", "logs");
98343
+ if (!existsSync33(dir)) mkdirSync24(dir, { recursive: true, mode: 493 });
97471
98344
  appendFileSync4(this.logPath, line, { mode: 420 });
97472
98345
  } catch {
97473
98346
  }
@@ -97576,8 +98449,8 @@ var init_startupProgress = __esm({
97576
98449
  });
97577
98450
 
97578
98451
  // src/connector-service.ts
97579
- import { resolve as resolve16, join as join29, dirname as dirname9 } from "node:path";
97580
- import { existsSync as existsSync33, readFileSync as readFileSync23, writeFileSync as writeFileSync19, mkdirSync as mkdirSync24, readdirSync as readdirSync13, cpSync as cpSync3 } from "node:fs";
98452
+ import { resolve as resolve16, join as join30, dirname as dirname9 } from "node:path";
98453
+ import { existsSync as existsSync34, readFileSync as readFileSync23, writeFileSync as writeFileSync19, mkdirSync as mkdirSync25, readdirSync as readdirSync13, cpSync as cpSync4 } from "node:fs";
97581
98454
  import { homedir as homedir19 } from "node:os";
97582
98455
  import { execSync as execSync4 } from "node:child_process";
97583
98456
  import { fileURLToPath as fileURLToPath5 } from "node:url";
@@ -97590,16 +98463,16 @@ function loadConnectors() {
97590
98463
  loadFromDir(builtinDir, connectors);
97591
98464
  const devDir = resolve16(process.cwd(), "packages", "cli", "connectors");
97592
98465
  if (devDir !== builtinDir) loadFromDir(devDir, connectors);
97593
- const userDir = join29(homedir19(), ".markus", "connectors");
98466
+ const userDir = join30(homedir19(), ".markus", "connectors");
97594
98467
  loadFromDir(userDir, connectors);
97595
98468
  return [...connectors.values()].filter((c) => c.platform !== "_template");
97596
98469
  }
97597
98470
  function loadFromDir(dir, map) {
97598
- if (!existsSync33(dir)) return;
98471
+ if (!existsSync34(dir)) return;
97599
98472
  for (const file of readdirSync13(dir)) {
97600
98473
  if (!file.endsWith(".json") || file.startsWith("_")) continue;
97601
98474
  try {
97602
- const raw = readFileSync23(join29(dir, file), "utf-8");
98475
+ const raw = readFileSync23(join30(dir, file), "utf-8");
97603
98476
  const desc = JSON.parse(raw);
97604
98477
  if (desc.platform) {
97605
98478
  map.set(desc.platform, desc);
@@ -97624,7 +98497,7 @@ function scanInstalledPlatforms() {
97624
98497
  };
97625
98498
  for (const p of c.detection.configPaths) {
97626
98499
  const expanded = expandHome(p);
97627
- if (existsSync33(expanded)) {
98500
+ if (existsSync34(expanded)) {
97628
98501
  result.installed = true;
97629
98502
  result.configPath = expanded;
97630
98503
  break;
@@ -97651,7 +98524,7 @@ function scanInstalledPlatforms() {
97651
98524
  }
97652
98525
  function readPlatformConfig(connector) {
97653
98526
  const configPath = expandHome(connector.integration.configPath);
97654
- if (!existsSync33(configPath)) return null;
98527
+ if (!existsSync34(configPath)) return null;
97655
98528
  try {
97656
98529
  const raw = readFileSync23(configPath, "utf-8");
97657
98530
  if (connector.integration.configFormat === "json5") {
@@ -97666,11 +98539,11 @@ function readPlatformConfig(connector) {
97666
98539
  function writePlatformConfig(connector, markusUrl, token) {
97667
98540
  const configPath = expandHome(connector.integration.configPath);
97668
98541
  const configDir = dirname9(configPath);
97669
- if (!existsSync33(configDir)) {
97670
- mkdirSync24(configDir, { recursive: true });
98542
+ if (!existsSync34(configDir)) {
98543
+ mkdirSync25(configDir, { recursive: true });
97671
98544
  }
97672
98545
  let config = {};
97673
- if (existsSync33(configPath)) {
98546
+ if (existsSync34(configPath)) {
97674
98547
  const existing = readPlatformConfig(connector);
97675
98548
  if (existing) config = existing;
97676
98549
  }
@@ -97691,24 +98564,24 @@ function installSkillTemplate(connector) {
97691
98564
  const skillDir = expandHome(connector.integration.skillDir);
97692
98565
  const templateName = connector.integration.skillTemplateName;
97693
98566
  const candidates = [
97694
- join29(homedir19(), ".markus", "templates", templateName),
98567
+ join30(homedir19(), ".markus", "templates", templateName),
97695
98568
  resolve16(process.cwd(), "templates", templateName),
97696
98569
  resolve16(__dirname4, "..", "templates", templateName)
97697
98570
  ];
97698
98571
  let sourceDir;
97699
98572
  for (const c of candidates) {
97700
- if (existsSync33(c)) {
98573
+ if (existsSync34(c)) {
97701
98574
  sourceDir = c;
97702
98575
  break;
97703
98576
  }
97704
98577
  }
97705
98578
  if (!sourceDir) return false;
97706
- const targetDir = join29(skillDir, templateName);
97707
- if (!existsSync33(targetDir)) {
97708
- mkdirSync24(targetDir, { recursive: true });
98579
+ const targetDir = join30(skillDir, templateName);
98580
+ if (!existsSync34(targetDir)) {
98581
+ mkdirSync25(targetDir, { recursive: true });
97709
98582
  }
97710
98583
  try {
97711
- cpSync3(sourceDir, targetDir, { recursive: true });
98584
+ cpSync4(sourceDir, targetDir, { recursive: true });
97712
98585
  return true;
97713
98586
  } catch {
97714
98587
  return false;
@@ -97758,7 +98631,7 @@ __export(init_exports, {
97758
98631
  registerInitCommand: () => registerInitCommand
97759
98632
  });
97760
98633
  import { resolve as resolve17 } from "node:path";
97761
- import { readFileSync as readFileSync24, existsSync as existsSync34, cpSync as cpSync4 } from "node:fs";
98634
+ import { readFileSync as readFileSync24, existsSync as existsSync35, cpSync as cpSync5 } from "node:fs";
97762
98635
  import { homedir as homedir20 } from "node:os";
97763
98636
  function registerInitCommand(program2) {
97764
98637
  program2.command("init").description("Setup wizard: configure LLM provider, API keys, and server settings").option("--force", "Overwrite existing configuration").option("--non-interactive", "Run without prompts (use env vars or --import-from)").option("--provider <name>", "LLM provider (anthropic/openai/google/minimax/siliconflow/zai/deepseek/ollama)").option("--api-key <key>", "LLM API key").option("--port <port>", "API server port", "8056").option("--import-from <platform>", "Import LLM config from an installed agent platform (e.g. openclaw, hermes)").option("--auto-connect", "Auto-connect detected agent platforms after init").action(async (opts) => {
@@ -97774,7 +98647,7 @@ function registerInitCommand(program2) {
97774
98647
  });
97775
98648
  }
97776
98649
  async function quickInit(options) {
97777
- const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync26 } = await import("node:fs");
98650
+ const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync27 } = await import("node:fs");
97778
98651
  const { join: pathJoin } = await import("node:path");
97779
98652
  const readline4 = await import("node:readline");
97780
98653
  const nonInteractive = options?.nonInteractive ?? false;
@@ -97795,7 +98668,7 @@ async function quickInit(options) {
97795
98668
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
97796
98669
  `);
97797
98670
  const configPath = getDefaultConfigPath();
97798
- if (existsSync34(configPath) && !options?.force) {
98671
+ if (existsSync35(configPath) && !options?.force) {
97799
98672
  if (nonInteractive) {
97800
98673
  console.log(` Existing configuration found. Use --force to overwrite.`);
97801
98674
  rl?.close();
@@ -97844,7 +98717,7 @@ async function quickInit(options) {
97844
98717
  pathJoin(homedir20(), ".openclaw", "openclaw.json5")
97845
98718
  ];
97846
98719
  for (const p of openclawCandidates) {
97847
- if (existsSync34(p)) {
98720
+ if (existsSync35(p)) {
97848
98721
  openclawPath = p;
97849
98722
  break;
97850
98723
  }
@@ -98048,15 +98921,15 @@ async function quickInit(options) {
98048
98921
  }
98049
98922
  const userTemplatesDir = pathJoin(homedir20(), ".markus", "templates");
98050
98923
  const builtinTemplatesDir = resolveTemplatesDir("roles");
98051
- if (builtinTemplatesDir && existsSync34(builtinTemplatesDir) && !existsSync34(userTemplatesDir)) {
98924
+ if (builtinTemplatesDir && existsSync35(builtinTemplatesDir) && !existsSync35(userTemplatesDir)) {
98052
98925
  const builtinRoot = resolve17(builtinTemplatesDir, "..");
98053
- mkdirSync26(userTemplatesDir, { recursive: true });
98054
- cpSync4(builtinRoot, userTemplatesDir, { recursive: true });
98926
+ mkdirSync27(userTemplatesDir, { recursive: true });
98927
+ cpSync5(builtinRoot, userTemplatesDir, { recursive: true });
98055
98928
  console.log(` Copied templates to ${userTemplatesDir}`);
98056
98929
  }
98057
98930
  const devRoleDir = pathJoin(userTemplatesDir || pathJoin(process.cwd(), "templates"), "roles", "developer");
98058
- if (!existsSync34(devRoleDir)) {
98059
- mkdirSync26(devRoleDir, { recursive: true });
98931
+ if (!existsSync35(devRoleDir)) {
98932
+ mkdirSync27(devRoleDir, { recursive: true });
98060
98933
  writeFileSync21(
98061
98934
  pathJoin(devRoleDir, "ROLE.md"),
98062
98935
  [
@@ -98993,15 +99866,15 @@ var start_exports = {};
98993
99866
  __export(start_exports, {
98994
99867
  registerStartCommand: () => registerStartCommand
98995
99868
  });
98996
- import { resolve as resolve18, join as join30, dirname as dirname10 } from "node:path";
98997
- import { existsSync as existsSync35, readFileSync as readFileSync25 } from "node:fs";
99869
+ import { resolve as resolve18, join as join31, dirname as dirname10 } from "node:path";
99870
+ import { existsSync as existsSync36, readFileSync as readFileSync25 } from "node:fs";
98998
99871
  import { homedir as homedir21 } from "node:os";
98999
99872
  function registerStartCommand(program2) {
99000
99873
  program2.command("start").description("Start the Markus server (auto-initializes on first run)").option("--setup", "Force re-run the interactive setup wizard before starting").action(async (opts) => {
99001
99874
  const globalOpts = program2.optsWithGlobals();
99002
99875
  const configPath = globalOpts.config ?? getDefaultConfigPath();
99003
- if (opts.setup || !existsSync35(configPath)) {
99004
- if (!existsSync35(configPath)) {
99876
+ if (opts.setup || !existsSync36(configPath)) {
99877
+ if (!existsSync36(configPath)) {
99005
99878
  console.log(" No configuration found \u2014 auto-configuring from environment...\n");
99006
99879
  }
99007
99880
  const { quickInit: quickInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -99130,8 +100003,8 @@ async function createServices(config) {
99130
100003
  extraSkillDirs: skillDirs
99131
100004
  });
99132
100005
  const storage = await initStorage(config.database?.url);
99133
- const markusDataDir = join30(homedir21(), ".markus");
99134
- const sharedDataDir = join30(markusDataDir, "shared");
100006
+ const markusDataDir = join31(homedir21(), ".markus");
100007
+ const sharedDataDir = join31(markusDataDir, "shared");
99135
100008
  const taskService = new TaskService();
99136
100009
  taskService.setSharedDataDir(sharedDataDir);
99137
100010
  if (storage) {
@@ -99163,7 +100036,7 @@ async function createServices(config) {
99163
100036
  const agentManager = new AgentManager({
99164
100037
  llmRouter,
99165
100038
  roleLoader,
99166
- dataDir: join30(markusDataDir, "agents"),
100039
+ dataDir: join31(markusDataDir, "agents"),
99167
100040
  sharedDataDir,
99168
100041
  skillRegistry,
99169
100042
  taskService,
@@ -99280,8 +100153,8 @@ async function startServer(config, values) {
99280
100153
  const extraPaths = [];
99281
100154
  const selfBinDir = dirname10(resolve18(process.argv[1] ?? ""));
99282
100155
  if (selfBinDir && !currentPath.includes(selfBinDir)) extraPaths.push(selfBinDir);
99283
- const cwdBin = join30(process.cwd(), "node_modules", ".bin");
99284
- if (existsSync35(cwdBin) && !currentPath.includes(cwdBin)) extraPaths.push(cwdBin);
100156
+ const cwdBin = join31(process.cwd(), "node_modules", ".bin");
100157
+ if (existsSync36(cwdBin) && !currentPath.includes(cwdBin)) extraPaths.push(cwdBin);
99285
100158
  if (extraPaths.length > 0) {
99286
100159
  process.env["PATH"] = `${extraPaths.join(":")}:${currentPath}`;
99287
100160
  }
@@ -99291,9 +100164,27 @@ async function startServer(config, values) {
99291
100164
  if (config.integrations?.search?.serperApiKey && !process.env["SERPER_API_KEY"]) {
99292
100165
  process.env["SERPER_API_KEY"] = config.integrations.search.serperApiKey;
99293
100166
  }
100167
+ if (config.integrations?.search?.tavilyApiKey && !process.env["TAVILY_API_KEY"]) {
100168
+ process.env["TAVILY_API_KEY"] = config.integrations.search.tavilyApiKey;
100169
+ }
100170
+ if (config.integrations?.search?.bingApiKey && !process.env["BING_SEARCH_API_KEY"]) {
100171
+ process.env["BING_SEARCH_API_KEY"] = config.integrations.search.bingApiKey;
100172
+ }
100173
+ if (config.integrations?.search?.googleSearchApiKey && !process.env["GOOGLE_SEARCH_API_KEY"]) {
100174
+ process.env["GOOGLE_SEARCH_API_KEY"] = config.integrations.search.googleSearchApiKey;
100175
+ }
100176
+ if (config.integrations?.search?.googleSearchCx && !process.env["GOOGLE_SEARCH_CX"]) {
100177
+ process.env["GOOGLE_SEARCH_CX"] = config.integrations.search.googleSearchCx;
100178
+ }
100179
+ if (config.integrations?.search?.serpApiKey && !process.env["SERPAPI_API_KEY"]) {
100180
+ process.env["SERPAPI_API_KEY"] = config.integrations.search.serpApiKey;
100181
+ }
99294
100182
  if (config.integrations?.search?.braveApiKey && !process.env["BRAVE_SEARCH_API_KEY"]) {
99295
100183
  process.env["BRAVE_SEARCH_API_KEY"] = config.integrations.search.braveApiKey;
99296
100184
  }
100185
+ if (config.integrations?.search?.exaApiKey && !process.env["EXA_API_KEY"]) {
100186
+ process.env["EXA_API_KEY"] = config.integrations.search.exaApiKey;
100187
+ }
99297
100188
  if (config.integrations?.search?.bochaApiKey && !process.env["BOCHA_API_KEY"]) {
99298
100189
  process.env["BOCHA_API_KEY"] = config.integrations.search.bochaApiKey;
99299
100190
  }
@@ -99328,7 +100219,7 @@ async function startServer(config, values) {
99328
100219
  projectService.setProjectRepo(storage.projectRepo);
99329
100220
  }
99330
100221
  await projectService.loadFromDB("default");
99331
- const knowledgeStore = new FileKnowledgeStore(join30(homedir21(), ".markus", "knowledge"));
100222
+ const knowledgeStore = new FileKnowledgeStore(join31(homedir21(), ".markus", "knowledge"));
99332
100223
  const knowledgeService = new KnowledgeService(knowledgeStore);
99333
100224
  const deliverableService = new DeliverableService(storage?.deliverableRepo);
99334
100225
  await deliverableService.load();
@@ -99358,7 +100249,6 @@ async function startServer(config, values) {
99358
100249
  requirementService.rebuildTaskLinks(taskService.listTasks());
99359
100250
  agentManager.setRequirementService(requirementService);
99360
100251
  agentManager.setProjectService(projectService);
99361
- agentManager.setKnowledgeService(knowledgeService);
99362
100252
  agentManager.setDeliverableService(deliverableService);
99363
100253
  agentManager.setWebUiBaseUrl(`http://localhost:${apiPort}`);
99364
100254
  requirementService.setAgentManager(agentManager);
@@ -99369,6 +100259,7 @@ async function startServer(config, values) {
99369
100259
  apiServer.setDeliverableService(deliverableService);
99370
100260
  apiServer.setRequirementService(requirementService);
99371
100261
  taskService.setDeliverableService(deliverableService);
100262
+ orgService.setDeliverableService(deliverableService);
99372
100263
  taskService.setProjectService(projectService);
99373
100264
  taskService.setRequirementService(requirementService);
99374
100265
  const archiveService = new ArchiveService(taskService, projectService);
@@ -99423,7 +100314,7 @@ async function startServer(config, values) {
99423
100314
  const builderService = apiServer.getBuilderService();
99424
100315
  if (builderService) {
99425
100316
  const builtinTeamsDir = resolveTemplatesDir("teams");
99426
- if (builtinTeamsDir && existsSync35(builtinTeamsDir)) {
100317
+ if (builtinTeamsDir && existsSync36(builtinTeamsDir)) {
99427
100318
  builderService.setBuiltinTeamTemplatesDir(builtinTeamsDir);
99428
100319
  }
99429
100320
  agentManager.setBuilderService(builderService);
@@ -99725,9 +100616,9 @@ ${reason}`;
99725
100616
  apiServer.setGateway(gateway, gatewaySecret);
99726
100617
  log87.info("External Agent Gateway enabled", { secret: gatewaySecret === "markus-gateway-default-secret-change-me" ? "(default)" : "(custom)" });
99727
100618
  {
99728
- const hubTokenPath = join30(homedir21(), ".markus", "hub-token");
100619
+ const hubTokenPath = join31(homedir21(), ".markus", "hub-token");
99729
100620
  const createRemoteAgent = async () => {
99730
- const token = existsSync35(hubTokenPath) ? readFileSync25(hubTokenPath, "utf-8").trim() : void 0;
100621
+ const token = existsSync36(hubTokenPath) ? readFileSync25(hubTokenPath, "utf-8").trim() : void 0;
99731
100622
  if (!token) return null;
99732
100623
  const { RemoteAccessAgent: RemoteAccessAgent2 } = await Promise.resolve().then(() => (init_dist8(), dist_exports6));
99733
100624
  return new RemoteAccessAgent2({
@@ -101115,8 +102006,8 @@ ${C3.BOLD}\u25C6 Summary${C3.RESET}
101115
102006
  }
101116
102007
  async function getDefaultConfigPath2() {
101117
102008
  const { homedir: homedir22 } = await import("node:os");
101118
- const { join: join31 } = await import("node:path");
101119
- return join31(homedir22(), ".markus", "markus.json");
102009
+ const { join: join32 } = await import("node:path");
102010
+ return join32(homedir22(), ".markus", "markus.json");
101120
102011
  }
101121
102012
  function registerDoctorCommand(program2) {
101122
102013
  program2.command("doctor").description("Diagnose Markus configuration issues and environment health").option("--fix", "Attempt to automatically fix issues").option("--verbose", "Show detailed output").action(async (opts) => {
@@ -101851,9 +102742,9 @@ ${C6.BOLD}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u25
101851
102742
  gateway.command("logs").description("Show recent gateway logs").option("--lines <n>", "Number of lines", "50").action(async (opts) => {
101852
102743
  const lines = parseInt(opts.lines ?? "50", 10);
101853
102744
  const { homedir: homedir22 } = await import("node:os");
101854
- const { join: join31 } = await import("node:path");
102745
+ const { join: join32 } = await import("node:path");
101855
102746
  const fs = await import("node:fs");
101856
- const logPath = join31(homedir22(), ".markus", "logs", "markus.log");
102747
+ const logPath = join32(homedir22(), ".markus", "logs", "markus.log");
101857
102748
  if (!fs.existsSync(logPath)) {
101858
102749
  warn3("No log file found", logPath);
101859
102750
  return;
@@ -103736,7 +104627,7 @@ var skill_exports = {};
103736
104627
  __export(skill_exports, {
103737
104628
  registerSkillCommands: () => registerSkillCommands
103738
104629
  });
103739
- import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync25 } from "node:fs";
104630
+ import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync26 } from "node:fs";
103740
104631
  import { resolve as resolve19 } from "node:path";
103741
104632
  function registerSkillCommands(program2) {
103742
104633
  const root = program2.command("skill").description("Skills registry and local scaffold");
@@ -103826,7 +104717,7 @@ function registerSkillCommands(program2) {
103826
104717
  const out = { json: !!g.json };
103827
104718
  if (!opts.dir && !opts.name) fail("Specify --dir or --name");
103828
104719
  const dir = resolve19(process.cwd(), opts.dir ?? opts.name);
103829
- mkdirSync25(dir, { recursive: true });
104720
+ mkdirSync26(dir, { recursive: true });
103830
104721
  const skillName = opts.name ?? (opts.dir ? opts.dir.replace(/.*[/\\]/, "") : "my-skill");
103831
104722
  const skillJson = {
103832
104723
  name: skillName,
@@ -103868,13 +104759,13 @@ __export(system_exports, {
103868
104759
  registerSystemCommands: () => registerSystemCommands
103869
104760
  });
103870
104761
  import { execSync as execSync6 } from "node:child_process";
103871
- import { existsSync as existsSync36, readFileSync as readFileSync26 } from "node:fs";
104762
+ import { existsSync as existsSync37, readFileSync as readFileSync26 } from "node:fs";
103872
104763
  import { resolve as resolve20, dirname as dirname11 } from "node:path";
103873
104764
  import { fileURLToPath as fileURLToPath6 } from "node:url";
103874
104765
  function findMarkusRoot() {
103875
104766
  let dir = dirname11(fileURLToPath6(import.meta.url));
103876
104767
  for (let i = 0; i < 10; i++) {
103877
- if (existsSync36(resolve20(dir, "package.json")) && existsSync36(resolve20(dir, "packages"))) return dir;
104768
+ if (existsSync37(resolve20(dir, "package.json")) && existsSync37(resolve20(dir, "packages"))) return dir;
103878
104769
  dir = dirname11(dir);
103879
104770
  }
103880
104771
  return null;
@@ -104021,7 +104912,7 @@ function registerSystemCommands(program2) {
104021
104912
  }
104022
104913
  }
104023
104914
  try {
104024
- const isGit = existsSync36(resolve20(markusRoot, ".git"));
104915
+ const isGit = existsSync37(resolve20(markusRoot, ".git"));
104025
104916
  if (isGit) {
104026
104917
  info4.gitBranch = execSync6("git rev-parse --abbrev-ref HEAD", { cwd: markusRoot, encoding: "utf-8" }).trim();
104027
104918
  info4.gitCommit = execSync6("git rev-parse --short HEAD", { cwd: markusRoot, encoding: "utf-8" }).trim();
@@ -104060,7 +104951,7 @@ function registerSystemCommands(program2) {
104060
104951
  fail("Cannot locate Markus installation directory");
104061
104952
  return;
104062
104953
  }
104063
- if (!existsSync36(resolve20(markusRoot, ".git"))) {
104954
+ if (!existsSync37(resolve20(markusRoot, ".git"))) {
104064
104955
  fail("Markus installation is not a git repository. Update manually.");
104065
104956
  return;
104066
104957
  }
@@ -104626,7 +105517,7 @@ var init_settings2 = __esm({
104626
105517
 
104627
105518
  // src/index.ts
104628
105519
  import { resolve as resolve21 } from "node:path";
104629
- import { readFileSync as readFileSync27, existsSync as existsSync37 } from "node:fs";
105520
+ import { readFileSync as readFileSync27, existsSync as existsSync38 } from "node:fs";
104630
105521
  import process2 from "node:process";
104631
105522
 
104632
105523
  // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
@@ -104650,7 +105541,7 @@ var {
104650
105541
  init_dist();
104651
105542
  init_output();
104652
105543
  var envPath = resolve21(process2.cwd(), ".env");
104653
- if (existsSync37(envPath)) {
105544
+ if (existsSync38(envPath)) {
104654
105545
  for (const line of readFileSync27(envPath, "utf-8").split("\n")) {
104655
105546
  const trimmed = line.trim();
104656
105547
  if (!trimmed || trimmed.startsWith("#")) continue;