@markus-global/cli 0.6.6 → 0.6.8

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, MAILBOX_QUEUED_TTL_MS, MAILBOX_PROCESSING_TIMEOUT_MS, WATCHDOG_INTERVAL_MS, WATCHDOG_DRIFT_THRESHOLD_MS, 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, 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, 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, MAILBOX_QUEUED_TTL_MS, MAILBOX_PROCESSING_TIMEOUT_MS, WATCHDOG_INTERVAL_MS, WATCHDOG_DRIFT_THRESHOLD_MS, 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_MAX_TOOL_ITERATIONS, DELIBERATION_ALLOWED_TOOLS, 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";
@@ -4292,14 +4292,41 @@ var init_limits = __esm({
4292
4292
  "memory_consolidation",
4293
4293
  "daily_report"
4294
4294
  ];
4295
- TRIAGE_MAX_TOOL_ITERATIONS = 3;
4295
+ TRIAGE_MAX_TOOL_ITERATIONS = 6;
4296
4296
  TRIAGE_ALLOWED_TOOLS = [
4297
4297
  "task_list",
4298
4298
  "task_get",
4299
4299
  "requirement_list",
4300
4300
  "requirement_get",
4301
4301
  "list_projects",
4302
- "team_list"
4302
+ "team_list",
4303
+ "recall_activity",
4304
+ "memory_search",
4305
+ "memory_search_longterm"
4306
+ ];
4307
+ DELIBERATION_MAX_TOOL_ITERATIONS = 10;
4308
+ DELIBERATION_ALLOWED_TOOLS = [
4309
+ // Context gathering (read-only)
4310
+ "task_list",
4311
+ "task_get",
4312
+ "requirement_list",
4313
+ "requirement_get",
4314
+ "list_projects",
4315
+ "team_list",
4316
+ "team_status",
4317
+ "recall_activity",
4318
+ "memory_search",
4319
+ "memory_search_longterm",
4320
+ // Inline action (lightweight communication)
4321
+ "notify_user",
4322
+ "task_comment",
4323
+ "requirement_comment",
4324
+ "agent_send_message",
4325
+ "agent_send_group_message",
4326
+ "agent_create_group_chat",
4327
+ "agent_list_group_chats",
4328
+ // Decision output
4329
+ "complete_deliberation"
4303
4330
  ];
4304
4331
  SUBAGENT_TASK_PREVIEW_CHARS = 500;
4305
4332
  SUBAGENT_THINKING_PREVIEW_CHARS = 1e3;
@@ -4311,6 +4338,9 @@ var init_limits = __esm({
4311
4338
  SUBAGENT_RETRY_BASE_MS = 2e3;
4312
4339
  SHELL_TIMEOUT_DEFAULT_MS = 6e4;
4313
4340
  SHELL_TIMEOUT_MAX_MS = 3e5;
4341
+ SHELL_MAX_SESSIONS_PER_AGENT = 3;
4342
+ SHELL_SESSION_IDLE_TIMEOUT_MS = 10 * 6e4;
4343
+ SHELL_SESSION_MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
4314
4344
  APPROVAL_WAIT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
4315
4345
  HEARTBEAT_MIN_INITIAL_DELAY_MS = 5e3;
4316
4346
  HEARTBEAT_STARTUP_JITTER_MS = 1e4;
@@ -4320,6 +4350,40 @@ var init_limits = __esm({
4320
4350
  }
4321
4351
  });
4322
4352
 
4353
+ // ../shared/dist/sanitize.js
4354
+ function sanitizeForLLM(text) {
4355
+ return text.replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(ANSI_OTHER, "").replace(C0_CONTROL, "").replace(C1_CONTROL, "").replace(LONE_HIGH_SURROGATE, "\uFFFD").replace(LONE_LOW_SURROGATE, "\uFFFD");
4356
+ }
4357
+ function safeSlice(text, start, end) {
4358
+ let result = text.slice(start, end);
4359
+ if (result.length === 0)
4360
+ return result;
4361
+ const first = result.charCodeAt(0);
4362
+ if (first >= 56320 && first <= 57343) {
4363
+ result = result.slice(1);
4364
+ }
4365
+ if (result.length === 0)
4366
+ return result;
4367
+ const last = result.charCodeAt(result.length - 1);
4368
+ if (last >= 55296 && last <= 56319) {
4369
+ result = result.slice(0, -1);
4370
+ }
4371
+ return result;
4372
+ }
4373
+ var ANSI_CSI, ANSI_OSC, ANSI_OTHER, C0_CONTROL, C1_CONTROL, LONE_HIGH_SURROGATE, LONE_LOW_SURROGATE;
4374
+ var init_sanitize = __esm({
4375
+ "../shared/dist/sanitize.js"() {
4376
+ "use strict";
4377
+ ANSI_CSI = /\x1b\[[0-9;]*[a-zA-Z]/g;
4378
+ ANSI_OSC = /\x1b\][^\x07]*(?:\x07|\x1b\\)/g;
4379
+ ANSI_OTHER = /\x1b[^[\]].?/g;
4380
+ C0_CONTROL = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
4381
+ C1_CONTROL = /[\x80-\x9f]/g;
4382
+ LONE_HIGH_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g;
4383
+ LONE_LOW_SURROGATE = /(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
4384
+ }
4385
+ });
4386
+
4323
4387
  // ../shared/dist/index.js
4324
4388
  var dist_exports = {};
4325
4389
  __export(dist_exports, {
@@ -4332,6 +4396,8 @@ __export(dist_exports, {
4332
4396
  COMPLETION_MARKER: () => COMPLETION_MARKER,
4333
4397
  COMPLETION_MARKER_INSTRUCTION: () => COMPLETION_MARKER_INSTRUCTION,
4334
4398
  CognitiveDepth: () => CognitiveDepth,
4399
+ DELIBERATION_ALLOWED_TOOLS: () => DELIBERATION_ALLOWED_TOOLS,
4400
+ DELIBERATION_MAX_TOOL_ITERATIONS: () => DELIBERATION_MAX_TOOL_ITERATIONS,
4335
4401
  DELIVERABLE_TITLE_CHARS: () => DELIVERABLE_TITLE_CHARS,
4336
4402
  ENTITY_COMMENTS_DEFAULT: () => ENTITY_COMMENTS_DEFAULT,
4337
4403
  HEARTBEAT_DAILY_LOG_CHARS: () => HEARTBEAT_DAILY_LOG_CHARS,
@@ -4368,6 +4434,9 @@ __export(dist_exports, {
4368
4434
  REVIEWER_FILE_LIST_MAX: () => REVIEWER_FILE_LIST_MAX,
4369
4435
  REVIEWER_NOTES_MAX: () => REVIEWER_NOTES_MAX,
4370
4436
  REVISION_REASON_CHARS: () => REVISION_REASON_CHARS,
4437
+ SHELL_MAX_SESSIONS_PER_AGENT: () => SHELL_MAX_SESSIONS_PER_AGENT,
4438
+ SHELL_SESSION_IDLE_TIMEOUT_MS: () => SHELL_SESSION_IDLE_TIMEOUT_MS,
4439
+ SHELL_SESSION_MAX_OUTPUT_BYTES: () => SHELL_SESSION_MAX_OUTPUT_BYTES,
4371
4440
  SHELL_TIMEOUT_DEFAULT_MS: () => SHELL_TIMEOUT_DEFAULT_MS,
4372
4441
  SHELL_TIMEOUT_MAX_MS: () => SHELL_TIMEOUT_MAX_MS,
4373
4442
  SUBAGENT_ERROR_PREVIEW_CHARS: () => SUBAGENT_ERROR_PREVIEW_CHARS,
@@ -4437,6 +4506,8 @@ __export(dist_exports, {
4437
4506
  orgId: () => orgId,
4438
4507
  readManifest: () => readManifest,
4439
4508
  requirementId: () => requirementId,
4509
+ safeSlice: () => safeSlice,
4510
+ sanitizeForLLM: () => sanitizeForLLM,
4440
4511
  saveConfig: () => saveConfig,
4441
4512
  stripInternalBlocks: () => stripInternalBlocks,
4442
4513
  taskId: () => taskId,
@@ -4468,6 +4539,7 @@ var init_dist = __esm({
4468
4539
  init_version();
4469
4540
  init_update_checker();
4470
4541
  init_limits();
4542
+ init_sanitize();
4471
4543
  }
4472
4544
  });
4473
4545
 
@@ -5464,6 +5536,10 @@ import { join as join6 } from "node:path";
5464
5536
  function freshCounters() {
5465
5537
  return {
5466
5538
  totalTokens: 0,
5539
+ promptTokens: 0,
5540
+ completionTokens: 0,
5541
+ estimatedCost: 0,
5542
+ costToday: 0,
5467
5543
  requestCount: 0,
5468
5544
  toolCalls: 0,
5469
5545
  errorCount: 0,
@@ -5507,6 +5583,7 @@ var init_agent_metrics = __esm({
5507
5583
  c.tokensToday = 0;
5508
5584
  c.requestsToday = 0;
5509
5585
  c.toolCallsToday = 0;
5586
+ c.costToday = 0;
5510
5587
  c.todayCutoffDate = today;
5511
5588
  }
5512
5589
  c.totalEvents++;
@@ -5517,6 +5594,12 @@ var init_agent_metrics = __esm({
5517
5594
  if (event.type === "llm_request") {
5518
5595
  const tokens = event.tokensUsed ?? 0;
5519
5596
  c.totalTokens += tokens;
5597
+ c.promptTokens += event.inputTokens ?? 0;
5598
+ c.completionTokens += event.outputTokens ?? 0;
5599
+ if (event.cost) {
5600
+ c.estimatedCost += event.cost;
5601
+ c.costToday += event.cost;
5602
+ }
5520
5603
  c.requestCount++;
5521
5604
  c.tokensToday += tokens;
5522
5605
  c.requestsToday++;
@@ -5575,19 +5658,29 @@ var init_agent_metrics = __esm({
5575
5658
  */
5576
5659
  getUsageStats() {
5577
5660
  const c = this.counters;
5578
- const input = Math.round(c.totalTokens * 0.7);
5579
- const output = c.totalTokens - input;
5580
- const estimatedCost = input / 1e6 * 3 + output / 1e6 * 15;
5661
+ let cost = c.estimatedCost;
5662
+ let costToday = c.costToday;
5663
+ if (cost === 0 && c.totalTokens > 0) {
5664
+ const input = Math.round(c.totalTokens * 0.7);
5665
+ const output = c.totalTokens - input;
5666
+ cost = input / 1e6 * 3 + output / 1e6 * 15;
5667
+ }
5668
+ if (costToday === 0 && c.tokensToday > 0) {
5669
+ const input = Math.round(c.tokensToday * 0.7);
5670
+ const output = c.tokensToday - input;
5671
+ costToday = input / 1e6 * 3 + output / 1e6 * 15;
5672
+ }
5581
5673
  return {
5582
5674
  totalTokens: c.totalTokens,
5583
- promptTokens: input,
5584
- completionTokens: output,
5675
+ promptTokens: c.promptTokens || Math.round(c.totalTokens * 0.7),
5676
+ completionTokens: c.completionTokens || c.totalTokens - Math.round(c.totalTokens * 0.7),
5585
5677
  requestCount: c.requestCount,
5586
5678
  toolCalls: c.toolCalls,
5587
5679
  tokensToday: c.tokensToday,
5588
5680
  requestsToday: c.requestsToday,
5589
5681
  toolCallsToday: c.toolCallsToday,
5590
- estimatedCost: Math.round(estimatedCost * 1e4) / 1e4
5682
+ estimatedCost: Math.round(cost * 1e4) / 1e4,
5683
+ costToday: Math.round(costToday * 1e4) / 1e4
5591
5684
  };
5592
5685
  }
5593
5686
  /**
@@ -5609,9 +5702,12 @@ var init_agent_metrics = __esm({
5609
5702
  return Math.round(Math.min(100, heartbeatComponent + taskComponent + errorComponent + recencyComponent));
5610
5703
  }
5611
5704
  computeTokenUsageFromCounters(c) {
5612
- const input = Math.round(c.totalTokens * 0.7);
5613
- const output = c.totalTokens - input;
5614
- const cost = input / 1e6 * 3 + output / 1e6 * 15;
5705
+ const input = c.promptTokens || Math.round(c.totalTokens * 0.7);
5706
+ const output = c.completionTokens || c.totalTokens - input;
5707
+ let cost = c.estimatedCost;
5708
+ if (cost === 0 && c.totalTokens > 0) {
5709
+ cost = input / 1e6 * 3 + output / 1e6 * 15;
5710
+ }
5615
5711
  return { input, output, cost: Math.round(cost * 1e4) / 1e4 };
5616
5712
  }
5617
5713
  computeTaskMetrics(tasks) {
@@ -6400,8 +6496,15 @@ var init_context_engine = __esm({
6400
6496
  stable.push("**Large file writing**: NEVER write a document >200 lines in a single `file_write` call. Write section by section: `file_write` the first section, then `file_edit` to append each subsequent section.");
6401
6497
  stable.push("**Error handling**: If a tool call fails, analyze the error and try a different approach \u2014 do NOT repeat the same failing action.");
6402
6498
  stable.push("**Subagent delegation**: For heavy subtasks needing many tool calls or lots of file reading, delegate to `spawn_subagent` to keep your context lean. Use `spawn_subagents` to run independent subtasks in parallel.");
6403
- stable.push("**Built-in tools over CLI**: ALWAYS prefer built-in tools (`task_create`, `task_assign`, `team_hire_agent`, `builder_install`, `agent_send_message`, `memory_save`, etc.) over running `markus` CLI commands via `shell_execute`. The CLI is for human operators \u2014 agents must use their native tool interface. Only fall back to CLI if no built-in tool exists for the operation.");
6404
- stable.push('**No auto-install/deploy**: NEVER automatically install or deploy agents, teams, or skills via `builder_install`, `team_hire_agent`, or `hub_install` unless explicitly requested by a human team member (e.g., "install", "deploy", "hire", "start"). Creating an artifact (writing files to `builder-artifacts/`) is separate from deploying it into the live organization.');
6499
+ stable.push("**Built-in tools over CLI**: ALWAYS prefer built-in tools (`task_create`, `task_assign`, `package_install`, `agent_send_message`, `memory_save`, etc.) over running `markus` CLI commands via `shell_execute`. The CLI is for human operators \u2014 agents must use their native tool interface. Only fall back to CLI if no built-in tool exists for the operation.");
6500
+ stable.push('**No auto-install/deploy**: NEVER automatically install or deploy agents, teams, or skills via `package_install` or `hub_install` unless explicitly requested by a human team member (e.g., "install", "deploy", "hire", "start"). Creating an artifact (writing files to `builder-artifacts/`) is separate from deploying it into the live organization.');
6501
+ stable.push("");
6502
+ stable.push("\n## Deliverable & Report Output Format");
6503
+ stable.push("When creating deliverable files (reports, analysis, documentation, etc.), choose the most appropriate format:");
6504
+ stable.push("- **Markdown (.md)**: Use for simple, short, or text-heavy content \u2014 READMEs, notes, status summaries, concise reports. Preferred when content is linear and doesn't need interactive elements.");
6505
+ stable.push("- **HTML (.html)**: Use for complex, data-rich, or interactive content \u2014 dashboards, multi-section reports with charts/tables, comparative analyses, visual summaries, or any content where layout, color-coding, or collapsible sections would help the reader absorb information faster. Include inline CSS for styling. You may embed lightweight JavaScript for interactivity (collapsible sections, sortable tables, tab switching, chart rendering via CDN libraries like Chart.js or ECharts).");
6506
+ stable.push("- **Other formats**: Use the format that best fits the content (e.g., JSON for structured data, CSV for tabular exports, SVG for diagrams).");
6507
+ stable.push("Default to markdown for brevity; escalate to HTML when richness helps comprehension. Always use the correct file extension so the platform can detect and render it properly.");
6405
6508
  stable.push("");
6406
6509
  stable.push("\n## Task & Requirement Workflow");
6407
6510
  stable.push("");
@@ -6747,6 +6850,8 @@ ${ctx.mergedContent.slice(0, SYSTEM_MAILBOX_MERGED_CHARS)}`);
6747
6850
  lines.push("**After creating tasks, STOP.** Do NOT execute the task work yourself. The task runs in its own isolated context after user approval. Reply with a summary of created tasks, assignees, and dependency structure. Tell the requester to review and approve.");
6748
6851
  lines.push("");
6749
6852
  lines.push("Keep responses concise and human-friendly. The user should not see raw tool outputs or complex operations.");
6853
+ lines.push("");
6854
+ lines.push("**Notification context**: Previous messages in this session may contain `<!-- notify_context: ... -->` metadata comments from your earlier `notify_user` calls. These embed `task_id`, `requirement_id`, and `priority` references. When the human replies to such a message, use these references to retrieve full context (e.g. via `recall_activity`, `search_tasks`) before responding.");
6750
6855
  break;
6751
6856
  case "task_execution":
6752
6857
  lines.push("You are in **task execution mode** \u2014 an isolated session for focused, thorough work.");
@@ -6875,6 +6980,40 @@ ${ctx.mergedContent.slice(0, SYSTEM_MAILBOX_MERGED_CHARS)}`);
6875
6980
  lines.push("Do NOT call any tools. Do NOT take any actions. Do NOT discuss tasks or projects.");
6876
6981
  lines.push("Respond with ONLY the JSON object as specified in the user message.");
6877
6982
  break;
6983
+ case "deliberation":
6984
+ lines.push("You are in **deliberation mode** \u2014 reviewing your mailbox before committing to work.");
6985
+ lines.push("");
6986
+ lines.push("**Purpose**: You have multiple pending items. Take a moment to understand the full picture before deciding what to focus on.");
6987
+ lines.push("");
6988
+ lines.push("**What you can do**:");
6989
+ lines.push("1. **Gather context**: Call tools like recall_activity, task_get, memory_search to understand history and commitments.");
6990
+ lines.push("2. **Handle simple items inline**: For trivial acknowledgments or quick notifications, use notify_user, task_comment, or agent_send_message now. Mark them as inline_completed in your final decision.");
6991
+ lines.push("3. **Choose your focus**: Decide which complex item deserves your full attention next.");
6992
+ lines.push("4. **Defer or drop**: Explicitly defer items for later or drop stale/redundant ones.");
6993
+ lines.push("");
6994
+ lines.push("**Rules**:");
6995
+ lines.push("- Human messages and comments are ALWAYS highest priority.");
6996
+ lines.push("- Consider your active tasks, pending commitments, and recent conversation context.");
6997
+ lines.push("- Stale informational items (old heartbeats, status updates) should be dropped aggressively.");
6998
+ lines.push("- When done, you MUST call the `complete_deliberation` tool with your decision.");
6999
+ lines.push("");
7000
+ lines.push("**Communication channel**: Your text output is NOT visible to anyone. Only tool calls have effect. To reach humans, use `notify_user`. To reach agents, use `agent_send_message`.");
7001
+ break;
7002
+ case "requirement_action":
7003
+ lines.push("You are processing a **requirement update that requires action** (e.g., all tasks completed and requirement needs review, or a human approved/rejected your proposed requirement).");
7004
+ lines.push("");
7005
+ lines.push("**Communication channel**: Your text output is **NOT visible** to any human or agent. You MUST use tools to take action and communicate. Simply writing text and ending your turn accomplishes nothing.");
7006
+ lines.push("");
7007
+ lines.push("**Required actions** \u2014 you MUST do at least one of the following:");
7008
+ lines.push("1. **Update requirement status**: `requirement_update_status` to mark the requirement as completed, or keep it active if more work is needed.");
7009
+ lines.push("2. **Create follow-up tasks**: `task_create` with the `requirement_id` if additional work is needed to fulfill the requirement.");
7010
+ lines.push("3. **Post a comment**: `requirement_comment` to leave notes or reasoning visible in the requirement thread.");
7011
+ lines.push("4. **Notify a human**: `notify_user` if the requirement outcome needs human attention or decision.");
7012
+ lines.push("");
7013
+ lines.push("**MANDATORY**: Before deciding, call `requirement_get` to understand the full current state of the requirement, including linked tasks, status, and comments.");
7014
+ lines.push("");
7015
+ lines.push("**CRITICAL**: You MUST call at least one action tool before finishing. Do NOT just output text \u2014 it will be lost.");
7016
+ break;
6878
7017
  }
6879
7018
  return lines.join("\n");
6880
7019
  }
@@ -7273,12 +7412,12 @@ ${heuristicSummary}
7273
7412
  return m;
7274
7413
  const headSize = Math.min(Math.floor(effectiveCap * 0.65), isResearch ? 4e3 : 1500);
7275
7414
  const tailSize = Math.min(Math.floor(effectiveCap * 0.25), isResearch ? 1500 : 800);
7276
- const head = text.slice(0, headSize);
7277
- const tail = text.slice(-tailSize);
7278
- const omitted = text.length - headSize - tailSize;
7415
+ const head = safeSlice(text, 0, headSize);
7416
+ const tail = safeSlice(text, text.length - tailSize);
7417
+ const omitted = text.length - head.length - tail.length;
7279
7418
  return {
7280
7419
  ...m,
7281
- content: `[Tool result compacted: showing ${headSize} head + ${tailSize} tail of ${text.length} chars.]
7420
+ content: `[Tool result compacted: showing ${head.length} head + ${tail.length} tail of ${text.length} chars.]
7282
7421
  ${head}
7283
7422
  [... ${omitted} chars omitted ...]
7284
7423
  ${tail}`
@@ -7288,7 +7427,7 @@ ${tail}`
7288
7427
  return m;
7289
7428
  return {
7290
7429
  ...m,
7291
- content: text.slice(0, maxChars) + `
7430
+ content: safeSlice(text, 0, maxChars) + `
7292
7431
 
7293
7432
  [... content trimmed from ${text.length} chars]`
7294
7433
  };
@@ -7748,6 +7887,7 @@ var init_cognitive2 = __esm({
7748
7887
  a2a: CognitiveDepth.D1_Reactive,
7749
7888
  comment_response: CognitiveDepth.D1_Reactive,
7750
7889
  review: CognitiveDepth.D1_Reactive,
7890
+ requirement_action: CognitiveDepth.D1_Reactive,
7751
7891
  task_execution: CognitiveDepth.D2_Deliberative
7752
7892
  };
7753
7893
  CognitivePreparation = class {
@@ -8255,7 +8395,7 @@ var init_tool_selector = __esm({
8255
8395
  "\u5206\u914D",
8256
8396
  "\u8DEF\u7531"
8257
8397
  ],
8258
- toolNames: ["team_list", "team_status", "delegate_message", "package_list", "package_install"]
8398
+ toolNames: ["team_list", "team_status", "delegate_message"]
8259
8399
  },
8260
8400
  {
8261
8401
  name: "deliverables",
@@ -8287,7 +8427,7 @@ var init_tool_selector = __esm({
8287
8427
  toolNames: ["deliverable_create", "deliverable_search", "deliverable_list", "deliverable_update"]
8288
8428
  },
8289
8429
  {
8290
- name: "builder",
8430
+ name: "packages",
8291
8431
  keywords: [
8292
8432
  "builder",
8293
8433
  "artifact",
@@ -8295,11 +8435,16 @@ var init_tool_selector = __esm({
8295
8435
  "skill",
8296
8436
  "package",
8297
8437
  "hub",
8438
+ "hire",
8439
+ "install agent",
8440
+ "install team",
8298
8441
  "\u90E8\u7F72",
8299
8442
  "\u5DE5\u4EF6",
8300
- "\u6280\u80FD\u5305"
8443
+ "\u6280\u80FD\u5305",
8444
+ "\u62DB\u8058",
8445
+ "\u5B89\u88C5"
8301
8446
  ],
8302
- toolNames: ["builder_install", "builder_list"]
8447
+ toolNames: ["package_list", "package_install", "hub_search", "hub_install"]
8303
8448
  }
8304
8449
  ];
8305
8450
  BASE_TOOL_NAMES = /* @__PURE__ */ new Set([
@@ -8331,11 +8476,12 @@ var init_tool_selector = __esm({
8331
8476
  selected.add(name);
8332
8477
  }
8333
8478
  if (opts.isManager) {
8334
- const managerGroup = this.groups.find((g) => g.name === "manager");
8335
- if (managerGroup) {
8336
- for (const name of managerGroup.toolNames) {
8337
- if (opts.allTools.has(name))
8338
- selected.add(name);
8479
+ for (const group of this.groups) {
8480
+ if (group.name === "manager" || group.name === "packages") {
8481
+ for (const name of group.toolNames) {
8482
+ if (opts.allTools.has(name))
8483
+ selected.add(name);
8484
+ }
8339
8485
  }
8340
8486
  }
8341
8487
  }
@@ -8426,7 +8572,8 @@ var init_tool_selector = __esm({
8426
8572
  title: { type: "string", description: "Short headline (1 line)" },
8427
8573
  body: { type: "string", description: "Full message content. Be thorough \u2014 this is what the recipient reads in chat." },
8428
8574
  priority: { type: "string", enum: ["low", "normal", "high", "urgent"], description: "Default: normal" },
8429
- related_task_id: { type: "string", description: "If related to a task, include the task ID for deep-linking" }
8575
+ related_task_id: { type: "string", description: "If related to a task, include the task ID for deep-linking" },
8576
+ target_user_id: { type: "string", description: "User ID to send the message to. If omitted, sends to the user you are currently interacting with (or the team owner)." }
8430
8577
  },
8431
8578
  required: ["title", "body"]
8432
8579
  }
@@ -8474,6 +8621,22 @@ var init_tool_selector = __esm({
8474
8621
  required: ["operation"]
8475
8622
  }
8476
8623
  });
8624
+ pushUnique({
8625
+ name: "complete_deliberation",
8626
+ 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.",
8627
+ inputSchema: {
8628
+ type: "object",
8629
+ properties: {
8630
+ process_item_id: { type: "string", description: "ID of the mailbox item to process in full next" },
8631
+ defer_item_ids: { type: "array", items: { type: "string" }, description: "IDs of items to defer for later" },
8632
+ drop_item_ids: { type: "array", items: { type: "string" }, description: "IDs of stale/redundant items to drop" },
8633
+ 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)" },
8634
+ reasoning: { type: "string", description: "1-2 sentence explanation of your decision" },
8635
+ situational_awareness: { type: "string", description: "Your current understanding of the situation \u2014 injected into future prompts for continuity" }
8636
+ },
8637
+ required: ["process_item_id", "reasoning"]
8638
+ }
8639
+ });
8477
8640
  log10.debug("Tool selection complete", {
8478
8641
  total: opts.allTools.size,
8479
8642
  selected: result.length,
@@ -8638,8 +8801,310 @@ var init_security = __esm({
8638
8801
  }
8639
8802
  });
8640
8803
 
8641
- // ../core/dist/tools/shell.js
8804
+ // ../core/dist/tools/shell-session.js
8642
8805
  import { spawn } from "node:child_process";
8806
+ import { randomBytes as randomBytes2 } from "node:crypto";
8807
+ function makeSentinel() {
8808
+ return SENTINEL_PREFIX + randomBytes2(8).toString("hex");
8809
+ }
8810
+ function getShellSessionManager() {
8811
+ if (!_defaultManager) {
8812
+ _defaultManager = new ShellSessionManager();
8813
+ }
8814
+ return _defaultManager;
8815
+ }
8816
+ var SENTINEL_PREFIX, ManagedSession, ShellSessionManager, _defaultManager;
8817
+ var init_shell_session = __esm({
8818
+ "../core/dist/tools/shell-session.js"() {
8819
+ "use strict";
8820
+ init_dist();
8821
+ SENTINEL_PREFIX = "__MARKUS_DONE_";
8822
+ ManagedSession = class {
8823
+ id;
8824
+ agentId;
8825
+ process;
8826
+ createdAt = Date.now();
8827
+ lastUsedAt = Date.now();
8828
+ alive = true;
8829
+ pending = null;
8830
+ dataBuffer = "";
8831
+ idleTimer = null;
8832
+ constructor(id, agentId2, proc) {
8833
+ this.id = id;
8834
+ this.agentId = agentId2;
8835
+ this.process = proc;
8836
+ proc.stdout?.on("data", (chunk) => this.onData(chunk.toString()));
8837
+ proc.stderr?.on("data", (chunk) => this.onData(chunk.toString()));
8838
+ proc.on("exit", () => {
8839
+ this.alive = false;
8840
+ this.rejectPending("Shell session exited unexpectedly");
8841
+ });
8842
+ proc.on("error", () => {
8843
+ this.alive = false;
8844
+ this.rejectPending("Shell session error");
8845
+ });
8846
+ this.resetIdleTimer();
8847
+ }
8848
+ onData(data) {
8849
+ if (!this.pending)
8850
+ return;
8851
+ this.pending.output += data;
8852
+ if (this.pending.output.length > SHELL_SESSION_MAX_OUTPUT_BYTES) {
8853
+ const keep = SHELL_SESSION_MAX_OUTPUT_BYTES - 1e3;
8854
+ this.pending.output = "[... truncated ...]\n" + this.pending.output.slice(-keep);
8855
+ }
8856
+ const sentinelRe = new RegExp(`${this.pending.sentinel}_(\\d+)_
8857
+ ?$`);
8858
+ const match2 = this.pending.output.match(sentinelRe);
8859
+ if (match2) {
8860
+ const exitCode = parseInt(match2[1], 10);
8861
+ const output = this.pending.output.slice(0, match2.index).replace(/^\n/, "");
8862
+ clearTimeout(this.pending.timer);
8863
+ const p = this.pending;
8864
+ this.pending = null;
8865
+ this.resetIdleTimer();
8866
+ p.resolve({ stdout: output, exitCode });
8867
+ }
8868
+ }
8869
+ rejectPending(reason) {
8870
+ if (this.pending) {
8871
+ clearTimeout(this.pending.timer);
8872
+ const p = this.pending;
8873
+ this.pending = null;
8874
+ p.resolve({ stdout: p.output + `
8875
+ [${reason}]`, exitCode: -1 });
8876
+ }
8877
+ }
8878
+ resetIdleTimer() {
8879
+ if (this.idleTimer)
8880
+ clearTimeout(this.idleTimer);
8881
+ this.idleTimer = setTimeout(() => this.kill(), SHELL_SESSION_IDLE_TIMEOUT_MS);
8882
+ }
8883
+ /**
8884
+ * Execute a command in this session. Returns when the sentinel is detected
8885
+ * or the timeout fires.
8886
+ */
8887
+ execute(command, timeoutMs, onOutput) {
8888
+ if (!this.alive) {
8889
+ return Promise.resolve({
8890
+ stdout: "[Session is no longer alive]",
8891
+ exitCode: -1
8892
+ });
8893
+ }
8894
+ if (this.pending) {
8895
+ return Promise.resolve({
8896
+ stdout: "[Session is busy with another command]",
8897
+ exitCode: -1
8898
+ });
8899
+ }
8900
+ this.lastUsedAt = Date.now();
8901
+ if (this.idleTimer)
8902
+ clearTimeout(this.idleTimer);
8903
+ const sentinel = makeSentinel();
8904
+ return new Promise((resolve20) => {
8905
+ const timer = setTimeout(() => {
8906
+ const partial = this.pending?.output ?? "";
8907
+ this.pending = null;
8908
+ this.resetIdleTimer();
8909
+ resolve20({
8910
+ stdout: partial + `
8911
+ [Command timed out after ${timeoutMs}ms]`,
8912
+ exitCode: -1
8913
+ });
8914
+ }, timeoutMs);
8915
+ this.pending = { sentinel, output: "", resolve: resolve20, timer };
8916
+ if (onOutput) {
8917
+ const origOnData = this.onData.bind(this);
8918
+ const origPendingRef = this.pending;
8919
+ let lastLen = 0;
8920
+ const outputPoll = setInterval(() => {
8921
+ if (this.pending !== origPendingRef) {
8922
+ clearInterval(outputPoll);
8923
+ return;
8924
+ }
8925
+ const newContent = this.pending.output.slice(lastLen);
8926
+ if (newContent) {
8927
+ const cleaned = newContent.replace(new RegExp(`echo ${sentinel}_\\$\\?_`), "");
8928
+ if (cleaned)
8929
+ onOutput(cleaned);
8930
+ lastLen = this.pending.output.length;
8931
+ }
8932
+ }, 200);
8933
+ const origResolve = this.pending.resolve;
8934
+ this.pending.resolve = (result) => {
8935
+ clearInterval(outputPoll);
8936
+ origResolve(result);
8937
+ };
8938
+ }
8939
+ const script = `${command}
8940
+ echo ${sentinel}_$?_
8941
+ `;
8942
+ this.process.stdin?.write(script);
8943
+ });
8944
+ }
8945
+ kill() {
8946
+ if (this.idleTimer)
8947
+ clearTimeout(this.idleTimer);
8948
+ this.alive = false;
8949
+ this.rejectPending("Session killed");
8950
+ try {
8951
+ this.process.kill("SIGTERM");
8952
+ setTimeout(() => {
8953
+ try {
8954
+ this.process.kill("SIGKILL");
8955
+ } catch {
8956
+ }
8957
+ }, 1e3);
8958
+ } catch {
8959
+ }
8960
+ }
8961
+ toInfo() {
8962
+ return {
8963
+ id: this.id,
8964
+ agentId: this.agentId,
8965
+ process: this.process,
8966
+ cwd: "",
8967
+ createdAt: this.createdAt,
8968
+ lastUsedAt: this.lastUsedAt,
8969
+ alive: this.alive
8970
+ };
8971
+ }
8972
+ };
8973
+ ShellSessionManager = class {
8974
+ sessions = /* @__PURE__ */ new Map();
8975
+ agentSessions = /* @__PURE__ */ new Map();
8976
+ /**
8977
+ * Get or create the default session for an agent.
8978
+ * If no session exists, one is lazily created.
8979
+ */
8980
+ getOrCreateDefault(agentId2, cwd) {
8981
+ const defaultId = `${agentId2}:default`;
8982
+ const existing = this.sessions.get(defaultId);
8983
+ if (existing?.alive)
8984
+ return existing;
8985
+ if (existing && !existing.alive) {
8986
+ this.removeSession(defaultId);
8987
+ }
8988
+ return this.createSession(defaultId, agentId2, cwd);
8989
+ }
8990
+ /**
8991
+ * Create a named session for an agent.
8992
+ */
8993
+ create(agentId2, sessionName, cwd) {
8994
+ const sessionId = `${agentId2}:${sessionName}`;
8995
+ if (this.sessions.has(sessionId)) {
8996
+ const s2 = this.sessions.get(sessionId);
8997
+ if (s2.alive)
8998
+ return s2;
8999
+ this.removeSession(sessionId);
9000
+ }
9001
+ const agentSet = this.agentSessions.get(agentId2) ?? /* @__PURE__ */ new Set();
9002
+ if (agentSet.size >= SHELL_MAX_SESSIONS_PER_AGENT) {
9003
+ return null;
9004
+ }
9005
+ return this.createSession(sessionId, agentId2, cwd);
9006
+ }
9007
+ get(sessionId) {
9008
+ const s2 = this.sessions.get(sessionId);
9009
+ if (s2 && !s2.alive) {
9010
+ this.removeSession(sessionId);
9011
+ return void 0;
9012
+ }
9013
+ return s2;
9014
+ }
9015
+ /**
9016
+ * Execute a command in the given session (or agent default).
9017
+ */
9018
+ async execute(agentId2, command, options) {
9019
+ const timeoutMs = Math.min(options?.timeoutMs ?? SHELL_TIMEOUT_DEFAULT_MS, SHELL_TIMEOUT_MAX_MS);
9020
+ let session;
9021
+ if (options?.sessionId) {
9022
+ session = this.get(options.sessionId);
9023
+ if (!session) {
9024
+ return { stdout: `[Session ${options.sessionId} not found]`, exitCode: -1 };
9025
+ }
9026
+ } else {
9027
+ session = this.getOrCreateDefault(agentId2, options?.cwd);
9028
+ }
9029
+ if (options?.cwd) {
9030
+ const cdResult = await session.execute(`cd ${JSON.stringify(options.cwd)} 2>/dev/null`, 5e3);
9031
+ if (cdResult.exitCode !== 0) {
9032
+ return { stdout: `[Failed to cd to ${options.cwd}]`, exitCode: -1 };
9033
+ }
9034
+ }
9035
+ return session.execute(command, timeoutMs, options?.onOutput);
9036
+ }
9037
+ listForAgent(agentId2) {
9038
+ const ids = this.agentSessions.get(agentId2);
9039
+ if (!ids)
9040
+ return [];
9041
+ const result = [];
9042
+ for (const id of ids) {
9043
+ const s2 = this.sessions.get(id);
9044
+ if (s2?.alive)
9045
+ result.push(s2.toInfo());
9046
+ }
9047
+ return result;
9048
+ }
9049
+ killSession(sessionId) {
9050
+ const s2 = this.sessions.get(sessionId);
9051
+ if (!s2)
9052
+ return false;
9053
+ s2.kill();
9054
+ this.removeSession(sessionId);
9055
+ return true;
9056
+ }
9057
+ killAllForAgent(agentId2) {
9058
+ const ids = this.agentSessions.get(agentId2);
9059
+ if (!ids)
9060
+ return;
9061
+ for (const id of [...ids]) {
9062
+ this.killSession(id);
9063
+ }
9064
+ }
9065
+ destroyAll() {
9066
+ for (const [id, s2] of this.sessions) {
9067
+ s2.kill();
9068
+ }
9069
+ this.sessions.clear();
9070
+ this.agentSessions.clear();
9071
+ }
9072
+ createSession(sessionId, agentId2, cwd) {
9073
+ const shell = process.env["SHELL"] || "/bin/sh";
9074
+ const child = spawn(shell, ["--norc", "--noprofile", "-i"], {
9075
+ cwd: cwd ?? process.cwd(),
9076
+ stdio: ["pipe", "pipe", "pipe"],
9077
+ env: {
9078
+ ...process.env,
9079
+ PS1: "",
9080
+ PS2: "",
9081
+ PROMPT_COMMAND: "",
9082
+ TERM: "dumb"
9083
+ }
9084
+ });
9085
+ const session = new ManagedSession(sessionId, agentId2, child);
9086
+ this.sessions.set(sessionId, session);
9087
+ const agentSet = this.agentSessions.get(agentId2) ?? /* @__PURE__ */ new Set();
9088
+ agentSet.add(sessionId);
9089
+ this.agentSessions.set(agentId2, agentSet);
9090
+ return session;
9091
+ }
9092
+ removeSession(sessionId) {
9093
+ const s2 = this.sessions.get(sessionId);
9094
+ if (s2) {
9095
+ const agentSet = this.agentSessions.get(s2.agentId);
9096
+ agentSet?.delete(sessionId);
9097
+ if (agentSet?.size === 0)
9098
+ this.agentSessions.delete(s2.agentId);
9099
+ }
9100
+ this.sessions.delete(sessionId);
9101
+ }
9102
+ };
9103
+ }
9104
+ });
9105
+
9106
+ // ../core/dist/tools/shell.js
9107
+ import { spawn as spawn2 } from "node:child_process";
8643
9108
  import { resolve as resolve4, normalize, sep } from "node:path";
8644
9109
  function injectGitCommitMeta(command, meta) {
8645
9110
  if (!meta)
@@ -8712,6 +9177,10 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8712
9177
  timeout_ms: {
8713
9178
  type: "number",
8714
9179
  description: `Timeout in milliseconds (default: ${SHELL_TIMEOUT_DEFAULT_MS}, max: ${SHELL_TIMEOUT_MAX_MS})`
9180
+ },
9181
+ session_id: {
9182
+ type: "string",
9183
+ description: "Reuse a persistent shell session by name. State (cwd, env vars, shell variables) persists across calls with the same session_id. Omit to use the default session."
8715
9184
  }
8716
9185
  },
8717
9186
  required: ["command"]
@@ -8768,22 +9237,71 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8768
9237
  }
8769
9238
  }
8770
9239
  const finalCommand = injectGitCommitMeta(command, agentMeta);
9240
+ const sessionId = args["session_id"];
9241
+ if (agentMeta?.agentId) {
9242
+ try {
9243
+ const mgr = getShellSessionManager();
9244
+ const result = await mgr.execute(agentMeta.agentId, finalCommand, {
9245
+ sessionId: sessionId ? `${agentMeta.agentId}:${sessionId}` : void 0,
9246
+ cwd: effectiveCwd ?? void 0,
9247
+ timeoutMs,
9248
+ onOutput: onOutput ? (chunk) => onOutput(sanitizeForLLM(chunk)) : void 0
9249
+ });
9250
+ const stdout = sanitizeForLLM(result.stdout);
9251
+ if (result.exitCode !== 0) {
9252
+ return JSON.stringify({
9253
+ status: "error",
9254
+ error: `Process exited with code ${result.exitCode}`,
9255
+ exitCode: result.exitCode,
9256
+ stdout: stdout.slice(0, 4e3)
9257
+ });
9258
+ }
9259
+ return JSON.stringify({
9260
+ status: "success",
9261
+ stdout: stdout.trim() || void 0
9262
+ });
9263
+ } catch {
9264
+ }
9265
+ }
8771
9266
  return new Promise((resolve20) => {
8772
- const child = spawn("sh", ["-c", finalCommand], {
9267
+ let settled = false;
9268
+ const settle = (result) => {
9269
+ if (settled)
9270
+ return;
9271
+ settled = true;
9272
+ resolve20(result);
9273
+ };
9274
+ const child = spawn2("sh", ["-c", finalCommand], {
8773
9275
  cwd: effectiveCwd ?? void 0,
8774
9276
  stdio: ["ignore", "pipe", "pipe"],
9277
+ detached: true,
8775
9278
  env: { ...process.env }
8776
9279
  });
9280
+ child.unref();
8777
9281
  let stdout = "";
8778
9282
  let stderr = "";
8779
9283
  let killed = false;
8780
9284
  const maxBuffer = 10 * 1024 * 1024;
8781
9285
  const timeout = setTimeout(() => {
8782
9286
  killed = true;
8783
- child.kill("SIGTERM");
9287
+ try {
9288
+ process.kill(-child.pid, "SIGTERM");
9289
+ } catch {
9290
+ }
8784
9291
  setTimeout(() => {
8785
- if (!child.killed)
8786
- child.kill("SIGKILL");
9292
+ try {
9293
+ process.kill(-child.pid, "SIGKILL");
9294
+ } catch {
9295
+ }
9296
+ child.stdout?.destroy();
9297
+ child.stderr?.destroy();
9298
+ settle(JSON.stringify({
9299
+ status: "error",
9300
+ error: `Command timed out after ${timeoutMs}ms`,
9301
+ exitCode: null,
9302
+ stdout: sanitizeForLLM(stdout).slice(0, 4e3),
9303
+ stderr: sanitizeForLLM(stderr).slice(0, 4e3)
9304
+ }));
8787
9305
  }, 2e3);
8788
9306
  }, timeoutMs);
8789
9307
  let outputBuffer = "";
@@ -8822,27 +9340,29 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8822
9340
  clearTimeout(flushTimer);
8823
9341
  flushOutput();
8824
9342
  }
9343
+ const cleanOut = sanitizeForLLM(stdout);
9344
+ const cleanErr = sanitizeForLLM(stderr);
8825
9345
  if (killed) {
8826
- resolve20(JSON.stringify({
9346
+ settle(JSON.stringify({
8827
9347
  status: "error",
8828
9348
  error: `Command timed out after ${timeoutMs}ms`,
8829
9349
  exitCode: code,
8830
- stdout: stdout.slice(0, 4e3),
8831
- stderr: stderr.slice(0, 4e3)
9350
+ stdout: cleanOut.slice(0, 4e3),
9351
+ stderr: cleanErr.slice(0, 4e3)
8832
9352
  }));
8833
9353
  } else if (code !== 0) {
8834
- resolve20(JSON.stringify({
9354
+ settle(JSON.stringify({
8835
9355
  status: "error",
8836
- error: stderr.trim() || `Process exited with code ${code}`,
9356
+ error: cleanErr.trim() || `Process exited with code ${code}`,
8837
9357
  exitCode: code,
8838
- stdout: stdout.slice(0, 4e3),
8839
- stderr: stderr.slice(0, 4e3)
9358
+ stdout: cleanOut.slice(0, 4e3),
9359
+ stderr: cleanErr.slice(0, 4e3)
8840
9360
  }));
8841
9361
  } else {
8842
- resolve20(JSON.stringify({
9362
+ settle(JSON.stringify({
8843
9363
  status: "success",
8844
- stdout: stdout.trim() || void 0,
8845
- stderr: stderr.trim() || void 0
9364
+ stdout: cleanOut.trim() || void 0,
9365
+ stderr: cleanErr.trim() || void 0
8846
9366
  }));
8847
9367
  }
8848
9368
  });
@@ -8852,11 +9372,11 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8852
9372
  clearTimeout(flushTimer);
8853
9373
  flushOutput();
8854
9374
  }
8855
- resolve20(JSON.stringify({
9375
+ settle(JSON.stringify({
8856
9376
  status: "error",
8857
9377
  error: err.message,
8858
- stdout: stdout.slice(0, 4e3),
8859
- stderr: stderr.slice(0, 4e3)
9378
+ stdout: sanitizeForLLM(stdout).slice(0, 4e3),
9379
+ stderr: sanitizeForLLM(stderr).slice(0, 4e3)
8860
9380
  }));
8861
9381
  });
8862
9382
  });
@@ -8869,6 +9389,7 @@ var init_shell = __esm({
8869
9389
  "use strict";
8870
9390
  init_dist();
8871
9391
  init_security();
9392
+ init_shell_session();
8872
9393
  GIT_ALWAYS_DENY = [];
8873
9394
  GIT_NEEDS_APPROVAL = [
8874
9395
  { pattern: /\bgit\s+push\s+--force/, label: "force push (--force)" },
@@ -41337,44 +41858,32 @@ async function searchBrave(query2, maxResults) {
41337
41858
  date: r.page_age
41338
41859
  }));
41339
41860
  }
41340
- async function searchBing(query2, maxResults) {
41341
- const encoded = encodeURIComponent(query2);
41861
+ async function searchBocha(query2, maxResults) {
41862
+ const apiKey = process.env["BOCHA_API_KEY"];
41863
+ if (!apiKey)
41864
+ throw new Error("BOCHA_API_KEY not configured");
41342
41865
  let res;
41343
41866
  try {
41344
- res = await proxyFetch(`https://www.bing.com/search?q=${encoded}`, {
41345
- headers: { "User-Agent": BING_UA }
41867
+ res = await proxyFetch("https://api.bochaai.com/v1/web-search", {
41868
+ method: "POST",
41869
+ headers: {
41870
+ "Authorization": `Bearer ${apiKey}`,
41871
+ "Content-Type": "application/json"
41872
+ },
41873
+ body: JSON.stringify({ query: query2, count: maxResults, summary: true })
41346
41874
  });
41347
41875
  } catch (err) {
41348
41876
  throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
41349
41877
  }
41350
41878
  if (!res.ok)
41351
41879
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
41352
- const html = await res.text();
41353
- const results = parseBingHtml(html, maxResults);
41354
- if (results.length === 0)
41355
- throw new Error("Parsed 0 results from Bing");
41356
- return results;
41357
- }
41358
- function parseBingHtml(html, max) {
41359
- const results = [];
41360
- const algoRegex = /<li[^>]*class="b_algo"[^>]*>([\s\S]*?)<\/li>/g;
41361
- let match2;
41362
- while ((match2 = algoRegex.exec(html)) !== null) {
41363
- if (results.length >= max)
41364
- break;
41365
- const item = match2[1];
41366
- const linkMatch = item.match(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/i);
41367
- if (!linkMatch)
41368
- continue;
41369
- const url = linkMatch[1];
41370
- const title = stripHtml(linkMatch[2]);
41371
- if (!url || !title)
41372
- continue;
41373
- const snippetMatch = item.match(/<p[^>]*>([\s\S]*?)<\/p>/i);
41374
- const snippet = snippetMatch ? stripHtml(snippetMatch[1]) : "";
41375
- results.push({ title, url, snippet });
41376
- }
41377
- return results;
41880
+ const data = await res.json();
41881
+ return (data.webPages?.value ?? []).slice(0, maxResults).map((r) => ({
41882
+ title: r.name,
41883
+ url: r.url,
41884
+ snippet: r.summary || r.snippet,
41885
+ date: r.datePublished
41886
+ }));
41378
41887
  }
41379
41888
  async function searchDuckDuckGo(query2, maxResults) {
41380
41889
  const encoded = encodeURIComponent(query2);
@@ -41464,7 +41973,7 @@ function parseDDGHtml(html, maxResults) {
41464
41973
  function stripHtml(html) {
41465
41974
  return html.replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
41466
41975
  }
41467
- var SEARCH_TIMEOUT_MS, _dispatcher, WebSearchTool, BING_UA, DDG_UA, DDG_ENDPOINTS;
41976
+ var SEARCH_TIMEOUT_MS, _dispatcher, WebSearchTool, DDG_UA, DDG_ENDPOINTS;
41468
41977
  var init_web_search = __esm({
41469
41978
  "../core/dist/tools/web-search.js"() {
41470
41979
  "use strict";
@@ -41493,7 +42002,7 @@ var init_web_search = __esm({
41493
42002
  const backends = [
41494
42003
  { name: "Serper", fn: searchSerper },
41495
42004
  { name: "Brave", fn: searchBrave },
41496
- { name: "Bing", fn: searchBing },
42005
+ { name: "Bocha", fn: searchBocha },
41497
42006
  { name: "DuckDuckGo", fn: searchDuckDuckGo }
41498
42007
  ];
41499
42008
  const errors = [];
@@ -41525,7 +42034,6 @@ var init_web_search = __esm({
41525
42034
  });
41526
42035
  }
41527
42036
  };
41528
- BING_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
41529
42037
  DDG_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
41530
42038
  DDG_ENDPOINTS = [
41531
42039
  "https://lite.duckduckgo.com/lite/",
@@ -42100,7 +42608,7 @@ var init_patch = __esm({
42100
42608
  });
42101
42609
 
42102
42610
  // ../core/dist/tools/process-manager.js
42103
- import { spawn as spawn2 } from "node:child_process";
42611
+ import { spawn as spawn3 } from "node:child_process";
42104
42612
  import { resolve as resolve7 } from "node:path";
42105
42613
  function onBackgroundCompletion(cb) {
42106
42614
  completionListeners.push(cb);
@@ -42168,7 +42676,7 @@ function createBackgroundExecTool(workspacePath) {
42168
42676
  return JSON.stringify({ status: "denied", error: "Working directory must be within workspace" });
42169
42677
  }
42170
42678
  const id = `bg_${++sessionCounter}_${Date.now()}`;
42171
- const child = spawn2("sh", ["-c", command], {
42679
+ const child = spawn3("sh", ["-c", command], {
42172
42680
  cwd: effectiveCwd,
42173
42681
  stdio: ["ignore", "pipe", "pipe"]
42174
42682
  });
@@ -43386,6 +43894,8 @@ var init_attention = __esm({
43386
43894
  triageToolHandlers;
43387
43895
  triageChatFn;
43388
43896
  lastTriageResult;
43897
+ /** True while a full-session deliberation is in progress (suppresses yield points). */
43898
+ isDeliberating = false;
43389
43899
  /** Tracks whether the last yield-point decision was 'cancel' vs 'preempt'. */
43390
43900
  lastYieldDecision;
43391
43901
  unsubscribeNewItem;
@@ -43528,50 +44038,22 @@ var init_attention = __esm({
43528
44038
  }
43529
44039
  }
43530
44040
  let triaged = false;
43531
- if (this.mailbox.depth > 0 && this.triageJudge && this.needsLLMTriage(item)) {
43532
- const triageResult = await this.performTriage(item);
43533
- if (triageResult) {
43534
- triaged = true;
43535
- if (triageResult.processItemId !== item.id) {
43536
- this.mailbox.putBack(item);
43537
- const chosen = this.mailbox.dequeueById(triageResult.processItemId);
43538
- if (chosen) {
43539
- item = chosen;
43540
- } else {
43541
- const redequeued = this.mailbox.dequeueById(item.id);
43542
- if (redequeued)
43543
- item = redequeued;
43544
- }
44041
+ if (this.mailbox.depth > 0 && this.needsLLMTriage(item) && (this.delegate?.performDeliberation || this.triageJudge)) {
44042
+ if (this.delegate?.performDeliberation) {
44043
+ const allItems = [item, ...this.mailbox.getQueuedItems()];
44044
+ this.isDeliberating = true;
44045
+ const deliberationResult = await this.delegate.performDeliberation(item, allItems);
44046
+ this.isDeliberating = false;
44047
+ if (deliberationResult) {
44048
+ triaged = true;
44049
+ item = this.applyDeliberationResult(item, deliberationResult);
43545
44050
  }
43546
- const queueSnapshot = this.mailbox.getQueuedItems();
43547
- const isProtected = (id) => {
43548
- const it = queueSnapshot.find((i) => i.id === id);
43549
- if (!it)
43550
- return false;
43551
- if (it.sourceType === "human_chat")
43552
- return true;
43553
- if (it.payload.extra?.triggerExecution)
43554
- return true;
43555
- return false;
43556
- };
43557
- for (const deferId of triageResult.deferItemIds) {
43558
- if (isProtected(deferId))
43559
- continue;
43560
- this.mailbox.defer(deferId);
43561
- }
43562
- for (const dropId of triageResult.dropItemIds) {
43563
- if (isProtected(dropId))
43564
- continue;
43565
- this.mailbox.drop(dropId);
44051
+ } else {
44052
+ const triageResult = await this.performTriage(item);
44053
+ if (triageResult) {
44054
+ triaged = true;
44055
+ item = this.applyTriageResult(item, triageResult);
43566
44056
  }
43567
- this.lastTriageResult = { ...triageResult, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
43568
- this.delegate?.onTriageCompleted?.(triageResult);
43569
- this.eventBus.emit("attention:triage", {
43570
- agentId: this.agentId,
43571
- triage: this.lastTriageResult
43572
- });
43573
- const triageDecision = this.recordDecision("triage", item, triageResult.reasoning);
43574
- this.delegate?.onDecisionMade(triageDecision);
43575
44057
  }
43576
44058
  }
43577
44059
  if (!triaged) {
@@ -43738,6 +44220,9 @@ var init_attention = __esm({
43738
44220
  * - 'merge' → item was absorbed into current work (caller may inject into session)
43739
44221
  */
43740
44222
  async checkYieldPoint() {
44223
+ if (this.isDeliberating) {
44224
+ return { decision: "continue" };
44225
+ }
43741
44226
  if (!this.interruptSignal || !this.currentFocus) {
43742
44227
  return { decision: "continue" };
43743
44228
  }
@@ -43953,8 +44438,10 @@ var init_attention = __esm({
43953
44438
  description: t.description,
43954
44439
  inputSchema: t.inputSchema
43955
44440
  }));
44441
+ const triageAgentName = ctx?.agentName ?? "Agent";
44442
+ const triageRoleHint = ctx?.agentRole ? ` Your role: ${ctx.agentRole}.` : "";
43956
44443
  const messages = [
43957
- { role: "system", content: "You are a mailbox triage assistant. You may call read-only tools to gather context before making your decision. When ready, output ONLY a single JSON object \u2014 no explanation, no markdown fences, no <think> tags. Start your response with {" },
44444
+ { role: "system", content: `You are ${triageAgentName}, deliberating over your mailbox.${triageRoleHint} You may call tools to gather context before making your decision. When ready, output ONLY a single JSON object \u2014 no explanation, no markdown fences, no <think> tags. Start your response with {` },
43958
44445
  { role: "user", content: prompt }
43959
44446
  ];
43960
44447
  let iterations = 0;
@@ -44022,6 +44509,120 @@ var init_attention = __esm({
44022
44509
  return null;
44023
44510
  }
44024
44511
  }
44512
+ /**
44513
+ * Apply a TriageResult to the mailbox: reorder, defer, drop items.
44514
+ * Returns the item to process next.
44515
+ */
44516
+ applyTriageResult(currentItem, triageResult) {
44517
+ let item = currentItem;
44518
+ if (triageResult.processItemId !== item.id) {
44519
+ this.mailbox.putBack(item);
44520
+ const chosen = this.mailbox.dequeueById(triageResult.processItemId);
44521
+ if (chosen) {
44522
+ item = chosen;
44523
+ } else {
44524
+ const redequeued = this.mailbox.dequeueById(currentItem.id);
44525
+ if (redequeued)
44526
+ item = redequeued;
44527
+ }
44528
+ }
44529
+ const queueSnapshot = this.mailbox.getQueuedItems();
44530
+ const isProtected = (id) => {
44531
+ const it = queueSnapshot.find((i) => i.id === id);
44532
+ if (!it)
44533
+ return false;
44534
+ if (it.sourceType === "human_chat")
44535
+ return true;
44536
+ if (it.payload.extra?.triggerExecution)
44537
+ return true;
44538
+ return false;
44539
+ };
44540
+ for (const deferId of triageResult.deferItemIds) {
44541
+ if (isProtected(deferId))
44542
+ continue;
44543
+ this.mailbox.defer(deferId);
44544
+ }
44545
+ for (const dropId of triageResult.dropItemIds) {
44546
+ if (isProtected(dropId))
44547
+ continue;
44548
+ this.mailbox.drop(dropId);
44549
+ }
44550
+ this.lastTriageResult = { ...triageResult, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
44551
+ this.delegate?.onTriageCompleted?.(triageResult);
44552
+ this.eventBus.emit("attention:triage", {
44553
+ agentId: this.agentId,
44554
+ triage: this.lastTriageResult
44555
+ });
44556
+ const triageDecision = this.recordDecision("triage", item, triageResult.reasoning);
44557
+ this.delegate?.onDecisionMade(triageDecision);
44558
+ return item;
44559
+ }
44560
+ /**
44561
+ * Apply a DeliberationResult to the mailbox: handle inline completions, reorder, defer, drop.
44562
+ * Returns the item to process next.
44563
+ */
44564
+ applyDeliberationResult(currentItem, result) {
44565
+ let item = currentItem;
44566
+ const queueSnapshot = this.mailbox.getQueuedItems();
44567
+ const isProtected = (id) => {
44568
+ const it = queueSnapshot.find((i) => i.id === id);
44569
+ if (!it)
44570
+ return false;
44571
+ if (it.sourceType === "human_chat")
44572
+ return true;
44573
+ if (it.payload.extra?.triggerExecution)
44574
+ return true;
44575
+ return false;
44576
+ };
44577
+ for (const completedId of result.inlineCompletedIds) {
44578
+ if (isProtected(completedId))
44579
+ continue;
44580
+ if (completedId === currentItem.id)
44581
+ continue;
44582
+ const completedItem = queueSnapshot.find((i) => i.id === completedId);
44583
+ this.mailbox.complete(completedId);
44584
+ if (completedItem) {
44585
+ this.recordDecision("complete", completedItem, "Handled inline during deliberation");
44586
+ }
44587
+ }
44588
+ if (result.processItemId !== item.id) {
44589
+ this.mailbox.putBack(item);
44590
+ const chosen = this.mailbox.dequeueById(result.processItemId);
44591
+ if (chosen) {
44592
+ item = chosen;
44593
+ } else {
44594
+ const redequeued = this.mailbox.dequeueById(currentItem.id);
44595
+ if (redequeued)
44596
+ item = redequeued;
44597
+ }
44598
+ }
44599
+ for (const deferId of result.deferItemIds) {
44600
+ if (isProtected(deferId))
44601
+ continue;
44602
+ this.mailbox.defer(deferId);
44603
+ }
44604
+ for (const dropId of result.dropItemIds) {
44605
+ if (isProtected(dropId))
44606
+ continue;
44607
+ this.mailbox.drop(dropId);
44608
+ }
44609
+ const triageEquivalent = {
44610
+ processItemId: result.processItemId,
44611
+ deferItemIds: result.deferItemIds,
44612
+ dropItemIds: result.dropItemIds,
44613
+ inlineCompletedIds: result.inlineCompletedIds,
44614
+ reasoning: result.reasoning
44615
+ };
44616
+ this.lastTriageResult = { ...triageEquivalent, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
44617
+ this.delegate?.onDeliberationCompleted?.(result);
44618
+ this.eventBus.emit("attention:triage", {
44619
+ agentId: this.agentId,
44620
+ triage: this.lastTriageResult
44621
+ });
44622
+ const triageDecision = this.recordDecision("triage", item, `[deliberation] ${result.reasoning}`);
44623
+ this.delegate?.onDecisionMade(triageDecision);
44624
+ return item;
44625
+ }
44025
44626
  buildTriagePrompt(headItem, ctx) {
44026
44627
  const agentName = ctx?.agentName ?? "Agent";
44027
44628
  const queuedItems = this.mailbox.getQueuedItems();
@@ -44143,6 +44744,7 @@ var init_attention = __esm({
44143
44744
  processedItemId: this.lastTriageResult.processItemId,
44144
44745
  deferredItemIds: this.lastTriageResult.deferItemIds,
44145
44746
  droppedItemIds: this.lastTriageResult.dropItemIds,
44747
+ inlineCompletedIds: this.lastTriageResult.inlineCompletedIds ?? [],
44146
44748
  timestamp: this.lastTriageResult.timestamp
44147
44749
  } : void 0
44148
44750
  };
@@ -44946,7 +45548,8 @@ var init_tool_loop_detector = __esm({
44946
45548
  detectors: {
44947
45549
  genericRepeat: true,
44948
45550
  pingPong: true,
44949
- noProgress: true
45551
+ noProgress: true,
45552
+ sameToolBurst: true
44950
45553
  }
44951
45554
  };
44952
45555
  ToolLoopDetector = class {
@@ -44985,6 +45588,11 @@ var init_tool_loop_detector = __esm({
44985
45588
  if (result.detected)
44986
45589
  return result;
44987
45590
  }
45591
+ if (this.config.detectors.sameToolBurst) {
45592
+ const result = this.detectSameToolBurst();
45593
+ if (result.detected)
45594
+ return result;
45595
+ }
44988
45596
  return { detected: false, severity: "none", pattern: "", message: "" };
44989
45597
  }
44990
45598
  /**
@@ -45079,6 +45687,38 @@ var init_tool_loop_detector = __esm({
45079
45687
  }
45080
45688
  return noDetection();
45081
45689
  }
45690
+ /**
45691
+ * Detects: a single tool called many times consecutively, even with different
45692
+ * args/results each time. Catches "semantic loops" where the agent retries
45693
+ * variations of the same approach (e.g., different shell commands to debug
45694
+ * the same failing server) without switching strategy.
45695
+ */
45696
+ detectSameToolBurst() {
45697
+ if (this.history.length < this.config.warningThreshold)
45698
+ return noDetection();
45699
+ const last = this.history[this.history.length - 1];
45700
+ let streak = 1;
45701
+ for (let i = this.history.length - 2; i >= 0; i--) {
45702
+ if (this.history[i].name === last.name) {
45703
+ streak++;
45704
+ } else {
45705
+ break;
45706
+ }
45707
+ }
45708
+ const burstCritical = this.config.criticalThreshold + 2;
45709
+ const burstWarning = this.config.warningThreshold + 2;
45710
+ if (streak >= burstCritical) {
45711
+ const msg = `Critical: "${last.name}" called ${streak} times consecutively (with varying arguments) \u2014 likely stuck in a retry loop`;
45712
+ log15.warn(msg);
45713
+ return { detected: true, severity: "critical", pattern: "sameToolBurst", message: msg };
45714
+ }
45715
+ if (streak >= burstWarning) {
45716
+ const msg = `Warning: "${last.name}" called ${streak} times consecutively \u2014 consider a different approach`;
45717
+ log15.warn(msg);
45718
+ return { detected: true, severity: "warning", pattern: "sameToolBurst", message: msg };
45719
+ }
45720
+ return noDetection();
45721
+ }
45082
45722
  reset() {
45083
45723
  this.history = [];
45084
45724
  }
@@ -45308,6 +45948,7 @@ var init_agent2 = __esm({
45308
45948
  semanticSearch;
45309
45949
  currentSessionId;
45310
45950
  currentInteractingUserId;
45951
+ pendingDeliberationResult;
45311
45952
  dbSessionMap = /* @__PURE__ */ new Map();
45312
45953
  orgContext;
45313
45954
  contextMdPath;
@@ -45716,6 +46357,12 @@ ${notification.stdoutTail}`);
45716
46357
  getAttentionController() {
45717
46358
  return this.attentionController;
45718
46359
  }
46360
+ /** Retrieve and clear the pending deliberation result (set by complete_deliberation tool). */
46361
+ consumeDeliberationResult() {
46362
+ const result = this.pendingDeliberationResult;
46363
+ this.pendingDeliberationResult = void 0;
46364
+ return result;
46365
+ }
45719
46366
  /**
45720
46367
  * Check yield point during task execution — called between LLM turns.
45721
46368
  * Returns decision info so the tool loop can act on preemption or merges.
@@ -45764,12 +46411,20 @@ ${notification.stdoutTail}`);
45764
46411
  const messages = this.currentSessionId ? this.memory.getRecentMessages(this.currentSessionId, TRIAGE_CONTEXT_MESSAGES_MAX * 2).filter((m) => m.role === "user" || m.role === "assistant").slice(-TRIAGE_CONTEXT_MESSAGES_MAX).map((m) => ({ role: m.role, content: typeof m.content === "string" ? m.content.slice(0, TRIAGE_CONTEXT_MSG_CHARS) : String(m.content).slice(0, TRIAGE_CONTEXT_MSG_CHARS) })) : [];
45765
46412
  const activities = this.recentActivitySummaries();
45766
46413
  const activeTaskIds = Array.from(this.activeTasks);
45767
- return { agentName: this.config.name, recentMainSessionMessages: messages, recentActivitySummaries: activities, activeTaskIds };
46414
+ return { agentName: this.config.name, agentRole: this.role.name, recentMainSessionMessages: messages, recentActivitySummaries: activities, activeTaskIds };
45768
46415
  },
45769
46416
  onTriageCompleted: (result) => {
45770
46417
  if (!result)
45771
46418
  return;
45772
46419
  this.updateCognition(result);
46420
+ },
46421
+ performDeliberation: async (headItem, allItems) => {
46422
+ return this.performDeliberation(headItem, allItems);
46423
+ },
46424
+ onDeliberationCompleted: (result) => {
46425
+ if (!result)
46426
+ return;
46427
+ this.updateCognitionFromDeliberation(result);
45773
46428
  }
45774
46429
  };
45775
46430
  }
@@ -45868,7 +46523,7 @@ ${notification.stdoutTail}`);
45868
46523
  case "requirement_update": {
45869
46524
  if (extra.actionRequired) {
45870
46525
  const reqId = item.payload.requirementId ?? "unknown";
45871
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `requirement_${reqId}_${ts}`, scenario: "requirement_action" }));
46526
+ const reply = await this.handleMessage(item.payload.content + COMPLETION_MARKER_INSTRUCTION, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `requirement_${reqId}_${ts}`, scenario: "requirement_action" }));
45872
46527
  resolveResponse(reply);
45873
46528
  return reply;
45874
46529
  }
@@ -45881,7 +46536,7 @@ ${notification.stdoutTail}`);
45881
46536
  }
45882
46537
  case "requirement_comment": {
45883
46538
  const reqId = item.payload.requirementId ?? "unknown";
45884
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `comment_${reqId}_${ts}`, scenario: "comment_response" }));
46539
+ const reply = await this.handleMessage(item.payload.content + COMPLETION_MARKER_INSTRUCTION, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `comment_${reqId}_${ts}`, scenario: "comment_response" }));
45885
46540
  resolveResponse(reply);
45886
46541
  return reply;
45887
46542
  }
@@ -45893,7 +46548,7 @@ ${notification.stdoutTail}`);
45893
46548
  this.injectUserMessage(sessionId, item.payload.content);
45894
46549
  } else {
45895
46550
  const commentTaskId = taskId2 ?? "unknown";
45896
- await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `comment_${commentTaskId}_${ts}`, scenario: "comment_response" }));
46551
+ await this.handleMessage(item.payload.content + COMPLETION_MARKER_INSTRUCTION, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `comment_${commentTaskId}_${ts}`, scenario: "comment_response" }));
45897
46552
  }
45898
46553
  resolveResponse("");
45899
46554
  return;
@@ -46401,7 +47056,7 @@ ${conversationText}`
46401
47056
  const filename = `${toolName}_${++this.toolResultCounter}_${Date.now()}.txt`;
46402
47057
  const filepath = join9(offloadDir, filename);
46403
47058
  writeFileSync8(filepath, result);
46404
- const preview = result.slice(0, previewSize);
47059
+ const preview = safeSlice(result, 0, previewSize);
46405
47060
  const lineCount = result.split("\n").length;
46406
47061
  return [
46407
47062
  `[FULL output (${result.length} chars, ${lineCount} lines) saved to: ${filepath}]`,
@@ -46410,11 +47065,11 @@ ${conversationText}`
46410
47065
  ``,
46411
47066
  preview,
46412
47067
  ``,
46413
- `[... remaining ${result.length - previewSize} chars in file ...]`
47068
+ `[... remaining ${result.length - preview.length} chars in file ...]`
46414
47069
  ].join("\n");
46415
47070
  } catch {
46416
47071
  const fallbackSize = isBrowserTool ? 3e4 : 8e3;
46417
- return result.slice(0, fallbackSize) + `
47072
+ return safeSlice(result, 0, fallbackSize) + `
46418
47073
 
46419
47074
  [... output truncated at ${fallbackSize} of ${result.length} total chars due to file-save failure ...]`;
46420
47075
  }
@@ -46629,6 +47284,109 @@ ${conversationText}`
46629
47284
  lines.push(`Reasoning: ${result.reasoning}`);
46630
47285
  this.currentCognition = lines.join("\n");
46631
47286
  }
47287
+ updateCognitionFromDeliberation(result) {
47288
+ if (result.situationalAwareness) {
47289
+ this.currentCognition = `## Current Situational Awareness (deliberation)
47290
+ ${result.situationalAwareness}`;
47291
+ } else {
47292
+ const lines = [];
47293
+ lines.push("## Current Situational Awareness (deliberation)");
47294
+ lines.push(`Decision: Processing item [${result.processItemId}]`);
47295
+ if (result.inlineCompletedIds.length > 0) {
47296
+ lines.push(`Handled ${result.inlineCompletedIds.length} item(s) inline`);
47297
+ }
47298
+ if (result.deferItemIds.length > 0) {
47299
+ lines.push(`Deferred ${result.deferItemIds.length} item(s)`);
47300
+ }
47301
+ if (result.dropItemIds.length > 0) {
47302
+ lines.push(`Dropped ${result.dropItemIds.length} item(s)`);
47303
+ }
47304
+ lines.push(`Reasoning: ${result.reasoning}`);
47305
+ this.currentCognition = lines.join("\n");
47306
+ }
47307
+ }
47308
+ /**
47309
+ * Full-session deliberation: the agent deliberates over all queued items as itself,
47310
+ * with access to its full tool set (filtered to deliberation-safe tools).
47311
+ */
47312
+ async performDeliberation(headItem, allItems) {
47313
+ const formatAge = (ms) => {
47314
+ if (ms < 6e4)
47315
+ return `${Math.round(ms / 1e3)}s`;
47316
+ if (ms < 36e5)
47317
+ return `${Math.round(ms / 6e4)}min`;
47318
+ return `${(ms / 36e5).toFixed(1)}h`;
47319
+ };
47320
+ const formatItem = (item, idx) => {
47321
+ const pri = PRIORITY_LABELS?.[item.priority] ?? `p${item.priority}`;
47322
+ const ageMs = Date.now() - new Date(item.queuedAt).getTime();
47323
+ const contentPreview = item.payload.content ? `
47324
+ content: "${item.payload.content.slice(0, TRIAGE_ITEM_CONTENT_CHARS)}"` : "";
47325
+ return [
47326
+ `[${idx + 1}] id="${item.id}"`,
47327
+ ` type=${item.sourceType} priority=${pri} age=${formatAge(ageMs)}`,
47328
+ ` summary: "${item.payload.summary}"`,
47329
+ item.payload.taskId ? ` taskId: ${item.payload.taskId}` : null,
47330
+ item.metadata?.senderName ? ` from: ${item.metadata.senderName} (${item.metadata?.senderRole ?? "unknown"})` : null,
47331
+ contentPreview || null
47332
+ ].filter(Boolean).join("\n");
47333
+ };
47334
+ const itemsSection = allItems.map(formatItem).join("\n\n");
47335
+ const deliberationPrompt = [
47336
+ "[DELIBERATION MODE]",
47337
+ "",
47338
+ `You have ${allItems.length} items in your mailbox that need attention. Before diving into work, take a moment to deliberate.`,
47339
+ "",
47340
+ "## Your Mailbox",
47341
+ itemsSection,
47342
+ "",
47343
+ "## What You Can Do",
47344
+ "1. **Gather context**: Use recall_activity, task_get, memory_search to understand the full picture.",
47345
+ "2. **Handle simple items inline**: For trivial acknowledgments, quick replies, or status notifications, handle them now using notify_user, task_comment, or agent_send_message. Mark them as inline_completed in your final decision.",
47346
+ "3. **Choose your focus**: Decide which complex item deserves your full attention next.",
47347
+ "4. **Defer or drop**: Explicitly defer items for later or drop stale/redundant ones.",
47348
+ "",
47349
+ "When ready, call `complete_deliberation` with your decision."
47350
+ ].join("\n");
47351
+ this.pendingDeliberationResult = void 0;
47352
+ const prevMailboxItemId = this.processingMailboxItemId;
47353
+ this.processingMailboxItemId = headItem.id;
47354
+ try {
47355
+ const allowedTools = new Set(DELIBERATION_ALLOWED_TOOLS);
47356
+ await this.handleMessage(deliberationPrompt, void 0, void 0, {
47357
+ sessionId: `deliberation_${this.id}_${Date.now()}`,
47358
+ scenario: "deliberation",
47359
+ maxToolIterations: DELIBERATION_MAX_TOOL_ITERATIONS,
47360
+ allowedTools
47361
+ });
47362
+ const result = this.consumeDeliberationResult();
47363
+ if (result) {
47364
+ log17.info("Deliberation completed", {
47365
+ agentId: this.id,
47366
+ processItemId: result.processItemId,
47367
+ inlineCompleted: result.inlineCompletedIds.length,
47368
+ deferred: result.deferItemIds.length,
47369
+ dropped: result.dropItemIds.length
47370
+ });
47371
+ for (const completedId of result.inlineCompletedIds) {
47372
+ const completedItem = allItems.find((i) => i.id === completedId);
47373
+ if (!completedItem)
47374
+ continue;
47375
+ const inlineActId = this.startActivity("inline_deliberation", `Handled inline: ${completedItem.payload.summary.slice(0, 80)}`, { mailboxItemId: completedId });
47376
+ this.emitActivityLog(inlineActId, "text", `This item was handled inline during deliberation. Reasoning: ${result.reasoning}`);
47377
+ this.endActivity(inlineActId);
47378
+ }
47379
+ return result;
47380
+ }
47381
+ log17.warn("Deliberation session ended without complete_deliberation call", { agentId: this.id });
47382
+ return null;
47383
+ } catch (err) {
47384
+ log17.warn("Deliberation failed", { agentId: this.id, error: String(err) });
47385
+ return null;
47386
+ } finally {
47387
+ this.processingMailboxItemId = prevMailboxItemId;
47388
+ }
47389
+ }
46632
47390
  getDynamicContext() {
46633
47391
  const parts = [...this.dynamicContextProviders.values()].map((p) => p()).filter(Boolean);
46634
47392
  for (const [name, instructions] of this.activatedSkillInstructions) {
@@ -46712,6 +47470,12 @@ ${instructions}
46712
47470
  getUsageStats() {
46713
47471
  return this.metricsCollector.getUsageStats();
46714
47472
  }
47473
+ computeCallCost(inputTokens, outputTokens) {
47474
+ const modelCost = this.llmRouter.getModelCost(this.getEffectiveProvider());
47475
+ if (!modelCost)
47476
+ return 0;
47477
+ return inputTokens / 1e6 * modelCost.input + outputTokens / 1e6 * modelCost.output;
47478
+ }
46715
47479
  emitAudit(event) {
46716
47480
  this.metricsCollector.recordAudit(event);
46717
47481
  this.auditCallback?.(event);
@@ -47130,12 +47894,16 @@ ${block}
47130
47894
  type: "llm_request",
47131
47895
  action: "chat",
47132
47896
  tokensUsed: tokensThisCall,
47897
+ inputTokens: response.usage.inputTokens,
47898
+ outputTokens: response.usage.outputTokens,
47899
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47133
47900
  durationMs: Date.now() - llmStart,
47134
47901
  success: true
47135
47902
  });
47136
47903
  let toolIterations = 0;
47137
47904
  const effectiveMaxIter = options?.maxToolIterations ?? this._maxToolIterations;
47138
47905
  const commentToolUsed = /* @__PURE__ */ new Set();
47906
+ const requirementActionToolUsed = /* @__PURE__ */ new Set();
47139
47907
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
47140
47908
  if (++toolIterations > effectiveMaxIter) {
47141
47909
  log17.warn("Tool loop hit max iterations", {
@@ -47236,6 +48004,10 @@ ${block}
47236
48004
  if (tc.name === "task_comment" || tc.name === "requirement_comment") {
47237
48005
  commentToolUsed.add(tc.name);
47238
48006
  }
48007
+ const REQ_ACTION_TOOLS = ["requirement_update_status", "requirement_comment", "task_create", "notify_user", "request_user_approval"];
48008
+ if (REQ_ACTION_TOOLS.includes(tc.name)) {
48009
+ requirementActionToolUsed.add(tc.name);
48010
+ }
47239
48011
  }
47240
48012
  const loopCheck = this.loopDetector.check();
47241
48013
  if (loopCheck.detected) {
@@ -47304,6 +48076,9 @@ ${chatYield.item.payload.content}`;
47304
48076
  type: "llm_request",
47305
48077
  action: "chat",
47306
48078
  tokensUsed: tokens2,
48079
+ inputTokens: response.usage.inputTokens,
48080
+ outputTokens: response.usage.outputTokens,
48081
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47307
48082
  durationMs: Date.now() - llmStart2,
47308
48083
  success: true
47309
48084
  });
@@ -47395,6 +48170,92 @@ ${chatYield.item.payload.content}`;
47395
48170
  }, this.getEffectiveProvider()), "Chat LLM comment-reminder-final");
47396
48171
  }
47397
48172
  }
48173
+ if (scenario === "requirement_action" && requirementActionToolUsed.size === 0 && toolIterations < effectiveMaxIter) {
48174
+ this.memory.appendMessage(sessionId, {
48175
+ role: "assistant",
48176
+ content: response.content,
48177
+ toolCalls: response.toolCalls,
48178
+ reasoningContent: response.reasoningContent
48179
+ });
48180
+ this.memory.appendMessage(sessionId, {
48181
+ role: "user",
48182
+ content: "[SYSTEM] You are about to end your turn WITHOUT taking any action. In requirement_action mode your text output is NOT visible to anyone. You MUST call at least one action tool: requirement_update_status, requirement_comment, task_create, or notify_user. Call requirement_get first if you need context, then take action."
48183
+ });
48184
+ const reminderMessages = this.memory.getRecentMessages(sessionId, maxHistory);
48185
+ const preparedReminder = await this.contextEngine.prepareMessages({
48186
+ systemPrompt,
48187
+ sessionMessages: reminderMessages,
48188
+ memory: this.memory,
48189
+ sessionId,
48190
+ agentId: this.id,
48191
+ modelContextWindow: this.llmRouter.getModelContextWindow(this.getEffectiveProvider()),
48192
+ modelMaxOutput: this.llmRouter.getModelMaxOutput(this.getEffectiveProvider()),
48193
+ toolDefinitions: llmTools,
48194
+ systemCacheSegments
48195
+ });
48196
+ response = await this.withNetworkRetry(() => this.llmRouter.chat({
48197
+ messages: preparedReminder.messages,
48198
+ tools: llmTools.length > 0 ? llmTools : void 0,
48199
+ metadata: this.getLLMMetadata(sessionId),
48200
+ compaction: useCompaction,
48201
+ systemCacheSegments
48202
+ }, this.getEffectiveProvider()), "Chat LLM requirement-action-reminder");
48203
+ if (response.finishReason === "tool_use" && response.toolCalls?.length) {
48204
+ const currentActId = this.state.currentActivity?.id;
48205
+ this.memory.appendMessage(sessionId, {
48206
+ role: "assistant",
48207
+ content: response.content,
48208
+ toolCalls: response.toolCalls,
48209
+ reasoningContent: response.reasoningContent
48210
+ });
48211
+ const toolResults = await Promise.all(response.toolCalls.map(async (tc) => {
48212
+ const toolStart = Date.now();
48213
+ if (currentActId) {
48214
+ this.emitActivityLog(currentActId, "tool_start", tc.name, { arguments: tc.arguments });
48215
+ }
48216
+ try {
48217
+ let result = await this.executeTool(tc, void 0, sessionId);
48218
+ result = this.offloadLargeResult(tc.name, result);
48219
+ if (currentActId) {
48220
+ this.emitActivityLog(currentActId, "tool_end", tc.name, {
48221
+ durationMs: Date.now() - toolStart,
48222
+ success: !isErrorResult2(result),
48223
+ arguments: tc.arguments,
48224
+ result
48225
+ });
48226
+ }
48227
+ return { toolCallId: tc.id, content: result, error: false };
48228
+ } catch (toolErr) {
48229
+ if (currentActId) {
48230
+ this.emitActivityLog(currentActId, "error", `Tool ${tc.name} failed: ${String(toolErr)}`);
48231
+ }
48232
+ return { toolCallId: tc.id, content: `Error: ${String(toolErr)}`, error: true };
48233
+ }
48234
+ }));
48235
+ for (const tr of toolResults) {
48236
+ this.memory.appendMessage(sessionId, { role: "tool", content: tr.content, toolCallId: tr.toolCallId });
48237
+ }
48238
+ const finalMessages = this.memory.getRecentMessages(sessionId, maxHistory);
48239
+ const preparedFinal = await this.contextEngine.prepareMessages({
48240
+ systemPrompt,
48241
+ sessionMessages: finalMessages,
48242
+ memory: this.memory,
48243
+ sessionId,
48244
+ agentId: this.id,
48245
+ modelContextWindow: this.llmRouter.getModelContextWindow(this.getEffectiveProvider()),
48246
+ modelMaxOutput: this.llmRouter.getModelMaxOutput(this.getEffectiveProvider()),
48247
+ toolDefinitions: llmTools,
48248
+ systemCacheSegments
48249
+ });
48250
+ response = await this.withNetworkRetry(() => this.llmRouter.chat({
48251
+ messages: preparedFinal.messages,
48252
+ tools: llmTools.length > 0 ? llmTools : void 0,
48253
+ metadata: this.getLLMMetadata(sessionId),
48254
+ compaction: useCompaction,
48255
+ systemCacheSegments
48256
+ }, this.getEffectiveProvider()), "Chat LLM requirement-action-reminder-final");
48257
+ }
48258
+ }
47398
48259
  const rawReply = sanitizeLLMReply(response.content);
47399
48260
  const displayReply = stripCompletionMarker(rawReply);
47400
48261
  const outputCheck = await this.guardrails.checkOutput(displayReply, { agentId: this.id });
@@ -47563,6 +48424,9 @@ ${chatYield.item.payload.content}`;
47563
48424
  type: "llm_request",
47564
48425
  action: "chat_stream",
47565
48426
  tokensUsed: tokensThisCall,
48427
+ inputTokens: response.usage.inputTokens,
48428
+ outputTokens: response.usage.outputTokens,
48429
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47566
48430
  durationMs: Date.now() - llmStart,
47567
48431
  success: true
47568
48432
  });
@@ -47741,6 +48605,9 @@ ${streamYield.item.payload.content}`;
47741
48605
  type: "llm_request",
47742
48606
  action: "chat_stream",
47743
48607
  tokensUsed: tokens2,
48608
+ inputTokens: response.usage.inputTokens,
48609
+ outputTokens: response.usage.outputTokens,
48610
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47744
48611
  durationMs: Date.now() - llmStart2,
47745
48612
  success: true
47746
48613
  });
@@ -48046,7 +48913,7 @@ ${streamYield.item.payload.content}`;
48046
48913
  let taskLlmTokens = response.usage.inputTokens + response.usage.outputTokens;
48047
48914
  this.updateTokensUsed(taskLlmTokens);
48048
48915
  this.calibrateTokenCounter(response.usage.inputTokens);
48049
- this.emitAudit({ type: "llm_request", action: "task_execution", tokensUsed: taskLlmTokens, durationMs: Date.now() - taskLlmStart, success: true });
48916
+ this.emitAudit({ type: "llm_request", action: "task_execution", tokensUsed: taskLlmTokens, inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens, cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens), durationMs: Date.now() - taskLlmStart, success: true });
48050
48917
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
48051
48918
  taskToolIterations++;
48052
48919
  if (cancelToken?.cancelled) {
@@ -48238,7 +49105,7 @@ ${yieldResult.item.payload.content}`
48238
49105
  taskLlmTokens = response.usage.inputTokens + response.usage.outputTokens;
48239
49106
  this.updateTokensUsed(taskLlmTokens);
48240
49107
  this.calibrateTokenCounter(response.usage.inputTokens);
48241
- this.emitAudit({ type: "llm_request", action: "task_execution", tokensUsed: taskLlmTokens, durationMs: Date.now() - taskLlmStart, success: true });
49108
+ this.emitAudit({ type: "llm_request", action: "task_execution", tokensUsed: taskLlmTokens, inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens, cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens), durationMs: Date.now() - taskLlmStart, success: true });
48242
49109
  }
48243
49110
  if (cancelToken?.cancelled) {
48244
49111
  flushText();
@@ -48478,7 +49345,7 @@ ${yieldResult.item.payload.content}`
48478
49345
  let risTokens = response.usage.inputTokens + response.usage.outputTokens;
48479
49346
  this.updateTokensUsed(risTokens);
48480
49347
  this.calibrateTokenCounter(response.usage.inputTokens);
48481
- this.emitAudit({ type: "llm_request", action: "respond_in_session", tokensUsed: risTokens, durationMs: Date.now() - risLlmStart, success: true });
49348
+ this.emitAudit({ type: "llm_request", action: "respond_in_session", tokensUsed: risTokens, inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens, cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens), durationMs: Date.now() - risLlmStart, success: true });
48482
49349
  let toolIter = 0;
48483
49350
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
48484
49351
  if (++toolIter > 200)
@@ -48507,6 +49374,7 @@ ${yieldResult.item.payload.content}`
48507
49374
  continue;
48508
49375
  }
48509
49376
  emit("tool_start", tc.name, { arguments: tc.arguments });
49377
+ this.emitActivityLog(actId, "tool_start", tc.name, { arguments: tc.arguments });
48510
49378
  const toolStart = Date.now();
48511
49379
  try {
48512
49380
  let result = await this.executeTool(tc, void 0, sessionId);
@@ -48514,11 +49382,13 @@ ${yieldResult.item.payload.content}`
48514
49382
  const isErr = isErrorResult2(result);
48515
49383
  const durationMs = Date.now() - toolStart;
48516
49384
  emit("tool_end", tc.name, { success: !isErr, durationMs, arguments: tc.arguments, result });
49385
+ this.emitActivityLog(actId, "tool_end", tc.name, { durationMs, success: !isErr, arguments: tc.arguments, result });
48517
49386
  this.emitAudit({ type: "tool_call", action: tc.name, durationMs, success: !isErr });
48518
49387
  this.memory.appendMessage(sessionId, { role: "tool", content: result, toolCallId: tc.id });
48519
49388
  } catch (toolErr) {
48520
49389
  const durationMs = Date.now() - toolStart;
48521
49390
  emit("tool_end", tc.name, { success: false, durationMs, arguments: tc.arguments, error: String(toolErr) });
49391
+ this.emitActivityLog(actId, "tool_end", tc.name, { durationMs, success: false, arguments: tc.arguments, error: String(toolErr) });
48522
49392
  this.emitAudit({ type: "tool_call", action: tc.name, durationMs, success: false });
48523
49393
  this.memory.appendMessage(sessionId, { role: "tool", content: `Error: ${String(toolErr)}`, toolCallId: tc.id });
48524
49394
  }
@@ -48540,11 +49410,14 @@ ${yieldResult.item.payload.content}`
48540
49410
  risTokens = response.usage.inputTokens + response.usage.outputTokens;
48541
49411
  this.updateTokensUsed(risTokens);
48542
49412
  this.calibrateTokenCounter(response.usage.inputTokens);
48543
- this.emitAudit({ type: "llm_request", action: "respond_in_session", tokensUsed: risTokens, durationMs: Date.now() - risLlmStart, success: true });
49413
+ this.emitAudit({ type: "llm_request", action: "respond_in_session", tokensUsed: risTokens, inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens, cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens), durationMs: Date.now() - risLlmStart, success: true });
48544
49414
  }
48545
49415
  flushText();
48546
49416
  const rawReply = sanitizeLLMReply(response.content);
48547
49417
  const displayReply = stripCompletionMarker(rawReply);
49418
+ if (displayReply.trim()) {
49419
+ this.emitActivityLog(actId, "text", displayReply);
49420
+ }
48548
49421
  this.memory.appendMessage(sessionId, { role: "assistant", content: displayReply, reasoningContent: response.reasoningContent });
48549
49422
  return rawReply;
48550
49423
  } catch (error) {
@@ -48828,9 +49701,20 @@ ${d.text}
48828
49701
  const explicitTaskId = toolCall.arguments.related_task_id;
48829
49702
  const taskId2 = explicitTaskId ?? this.getCurrentTaskId();
48830
49703
  const requirementId2 = this.getCurrentRequirementId();
49704
+ const explicitTargetUser = toolCall.arguments.target_user_id;
49705
+ const contextParts = [];
49706
+ if (taskId2)
49707
+ contextParts.push(`task_id=${taskId2}`);
49708
+ if (requirementId2)
49709
+ contextParts.push(`requirement_id=${requirementId2}`);
49710
+ if (priority && priority !== "normal")
49711
+ contextParts.push(`priority=${priority}`);
49712
+ const contextSuffix = contextParts.length > 0 ? `
49713
+
49714
+ <!-- notify_context: ${contextParts.join(", ")} -->` : "";
48831
49715
  const formattedMsg = `**${title}**
48832
49716
 
48833
- ${body}`;
49717
+ ${body}${contextSuffix}`;
48834
49718
  if (this.currentSessionId) {
48835
49719
  this.memory.appendMessage(this.currentSessionId, {
48836
49720
  role: "assistant",
@@ -48844,7 +49728,7 @@ ${body}`;
48844
49728
  this.eventBus.emit("agent:notify-user", {
48845
49729
  agentId: this.id,
48846
49730
  sessionId: this.currentSessionId,
48847
- targetUserId: this.currentInteractingUserId,
49731
+ targetUserId: explicitTargetUser ?? this.currentInteractingUserId,
48848
49732
  title,
48849
49733
  body,
48850
49734
  priority,
@@ -48897,6 +49781,21 @@ ${body}`;
48897
49781
  return JSON.stringify({ status: "error", message: `Failed to get user approval: ${String(err)}` });
48898
49782
  }
48899
49783
  }
49784
+ if (toolCall.name === "complete_deliberation") {
49785
+ const processItemId = toolCall.arguments.process_item_id;
49786
+ if (!processItemId) {
49787
+ return JSON.stringify({ status: "error", message: "process_item_id is required" });
49788
+ }
49789
+ this.pendingDeliberationResult = {
49790
+ processItemId,
49791
+ deferItemIds: toolCall.arguments.defer_item_ids ?? [],
49792
+ dropItemIds: toolCall.arguments.drop_item_ids ?? [],
49793
+ inlineCompletedIds: toolCall.arguments.inline_completed_ids ?? [],
49794
+ reasoning: toolCall.arguments.reasoning ?? "",
49795
+ situationalAwareness: toolCall.arguments.situational_awareness
49796
+ };
49797
+ return JSON.stringify({ status: "ok", message: "Deliberation decision recorded. Proceeding to focused processing." });
49798
+ }
48900
49799
  const profile = this.config.profile;
48901
49800
  if (profile) {
48902
49801
  if (profile.toolWhitelist && !profile.toolWhitelist.includes(toolCall.name)) {
@@ -49168,6 +50067,9 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
49168
50067
  "**Report format** \u2014 create a deliverable via `deliverable_create`:",
49169
50068
  `- **title**: "Daily Report \u2014 ${todayDate}"`,
49170
50069
  '- **type**: "file"',
50070
+ "- **format**: Choose based on content complexity:",
50071
+ ' - Use **"markdown"** (file extension `.md`) for simple, text-heavy reports with few metrics',
50072
+ ' - Use **"html"** (file extension `.html`) when the report includes dashboards, tables with many columns, charts, color-coded status indicators, or interactive elements that help readers quickly grasp team status',
49171
50073
  "- **Content must be concise, clear, and accurate**:",
49172
50074
  " 1. **My work today**: What you personally accomplished (tasks reviewed, approved/rejected, decisions made)",
49173
50075
  " 2. **My team progress**: What each member of YOUR team accomplished today (use `team_status` to check). Focus on your own team only.",
@@ -49326,11 +50228,11 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
49326
50228
  "notify_user",
49327
50229
  "request_user_approval",
49328
50230
  "recall_activity",
49329
- "builder_install",
49330
- "builder_list"
50231
+ "package_install",
50232
+ "package_list"
49331
50233
  ];
49332
50234
  if (isManager) {
49333
- baseTools.push("task_board_health", "task_cleanup_duplicates", "task_assign", "team_status", "deliverable_create", "deliverable_search", "package_install", "package_list");
50235
+ baseTools.push("task_board_health", "task_cleanup_duplicates", "task_assign", "team_status", "deliverable_create", "deliverable_search");
49334
50236
  }
49335
50237
  const HEARTBEAT_ALLOWED_TOOLS = new Set(baseTools);
49336
50238
  const HEARTBEAT_MAX_RETRIES = 3;
@@ -49874,21 +50776,52 @@ ${sharedContent}` : roleContent;
49874
50776
  });
49875
50777
 
49876
50778
  // ../core/dist/tools/mcp-client.js
49877
- import { spawn as spawn3 } from "node:child_process";
49878
- var log18, MCPClientManager;
50779
+ import { spawn as spawn4 } from "node:child_process";
50780
+ var log18, DEFAULT_IDLE_TIMEOUT_MS, MCPClientManager;
49879
50781
  var init_mcp_client = __esm({
49880
50782
  "../core/dist/tools/mcp-client.js"() {
49881
50783
  "use strict";
49882
50784
  init_dist();
49883
50785
  log18 = createLogger("mcp-client");
50786
+ DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
49884
50787
  MCPClientManager = class _MCPClientManager {
49885
50788
  servers = /* @__PURE__ */ new Map();
50789
+ serverConfigs = /* @__PURE__ */ new Map();
49886
50790
  requestId = 0;
49887
50791
  pendingRequests = /* @__PURE__ */ new Map();
49888
50792
  stdoutBuffers = /* @__PURE__ */ new Map();
50793
+ idleTimers = /* @__PURE__ */ new Map();
50794
+ idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS;
49889
50795
  static scopedKey(name, scopeId) {
49890
50796
  return `${name}::${scopeId}`;
49891
50797
  }
50798
+ setIdleTimeout(ms) {
50799
+ this.idleTimeoutMs = ms;
50800
+ }
50801
+ resetIdleTimer(key2) {
50802
+ const existing = this.idleTimers.get(key2);
50803
+ if (existing)
50804
+ clearTimeout(existing);
50805
+ if (this.idleTimeoutMs <= 0)
50806
+ return;
50807
+ const timer = setTimeout(() => {
50808
+ this.idleTimers.delete(key2);
50809
+ if (this.servers.has(key2)) {
50810
+ log18.info(`MCP server idle timeout reached, disconnecting: ${key2}`);
50811
+ this.disconnectServer(key2).catch(() => {
50812
+ });
50813
+ }
50814
+ }, this.idleTimeoutMs);
50815
+ timer.unref();
50816
+ this.idleTimers.set(key2, timer);
50817
+ }
50818
+ clearIdleTimer(key2) {
50819
+ const timer = this.idleTimers.get(key2);
50820
+ if (timer) {
50821
+ clearTimeout(timer);
50822
+ this.idleTimers.delete(key2);
50823
+ }
50824
+ }
49892
50825
  /**
49893
50826
  * Spawn an MCP server process, run JSON-RPC init handshake, list tools,
49894
50827
  * and store under the given internal key. Shared by connect and connectScoped.
@@ -49899,8 +50832,9 @@ var init_mcp_client = __esm({
49899
50832
  log18.info(`MCP server ${displayName} already connected, reusing (${existing.tools.length} tools)`);
49900
50833
  return existing.tools;
49901
50834
  }
50835
+ this.serverConfigs.set(key2, { displayName, config });
49902
50836
  log18.info(`Connecting to MCP server: ${displayName}`, { command: config.command, key: key2 });
49903
- const proc = spawn3(config.command, config.args ?? [], {
50837
+ const proc = spawn4(config.command, config.args ?? [], {
49904
50838
  stdio: ["pipe", "pipe", "pipe"],
49905
50839
  env: { ...process.env, ...config.env },
49906
50840
  shell: process.platform === "win32"
@@ -49925,6 +50859,7 @@ var init_mcp_client = __esm({
49925
50859
  }
49926
50860
  this.servers.delete(key2);
49927
50861
  this.stdoutBuffers.delete(proc);
50862
+ this.clearIdleTimer(key2);
49928
50863
  for (const [id, req] of this.pendingRequests) {
49929
50864
  if (req.proc !== proc)
49930
50865
  continue;
@@ -49945,6 +50880,7 @@ var init_mcp_client = __esm({
49945
50880
  const toolsResult = await this.sendRequest(proc, "tools/list", {});
49946
50881
  const tools = toolsResult?.tools ?? [];
49947
50882
  this.servers.set(key2, { process: proc, tools });
50883
+ this.resetIdleTimer(key2);
49948
50884
  log18.info(`MCP server ${displayName} connected with ${tools.length} tools`);
49949
50885
  return tools;
49950
50886
  } catch (err) {
@@ -49964,19 +50900,27 @@ var init_mcp_client = __esm({
49964
50900
  const key2 = _MCPClientManager.scopedKey(name, scopeId);
49965
50901
  return this.connectByKey(key2, `${name}[${scopeId}]`, config);
49966
50902
  }
49967
- callToolByKey(key2, toolName, args) {
49968
- const server = this.servers.get(key2);
49969
- if (!server)
49970
- throw new Error(`MCP server not found: ${key2}`);
49971
- return this.sendRequest(server.process, "tools/call", {
50903
+ async callToolByKey(key2, toolName, args) {
50904
+ let server = this.servers.get(key2);
50905
+ if (!server) {
50906
+ const saved = this.serverConfigs.get(key2);
50907
+ if (saved) {
50908
+ log18.info(`MCP server ${key2} not running, auto-reconnecting...`);
50909
+ await this.connectByKey(key2, saved.displayName, saved.config);
50910
+ server = this.servers.get(key2);
50911
+ }
50912
+ if (!server)
50913
+ throw new Error(`MCP server not found: ${key2}`);
50914
+ }
50915
+ this.resetIdleTimer(key2);
50916
+ const result = await this.sendRequest(server.process, "tools/call", {
49972
50917
  name: toolName,
49973
50918
  arguments: args
49974
- }).then((result) => {
49975
- const content = result?.content;
49976
- if (content?.[0]?.text)
49977
- return content[0].text;
49978
- return JSON.stringify(result);
49979
50919
  });
50920
+ const content = result?.content;
50921
+ if (content?.[0]?.text)
50922
+ return content[0].text;
50923
+ return JSON.stringify(result);
49980
50924
  }
49981
50925
  async callTool(serverName, toolName, args) {
49982
50926
  return this.callToolByKey(serverName, toolName, args);
@@ -50017,6 +50961,7 @@ var init_mcp_client = __esm({
50017
50961
  async disconnectServer(name) {
50018
50962
  const server = this.servers.get(name);
50019
50963
  if (server) {
50964
+ this.clearIdleTimer(name);
50020
50965
  server.process.kill();
50021
50966
  this.servers.delete(name);
50022
50967
  this.stdoutBuffers.delete(server.process);
@@ -50030,6 +50975,7 @@ var init_mcp_client = __esm({
50030
50975
  /**
50031
50976
  * Disconnect all scoped server instances belonging to a given scope (e.g. agent).
50032
50977
  * Shared (non-scoped) servers are not affected.
50978
+ * The server configs are retained so the server can auto-reconnect on next tool call.
50033
50979
  */
50034
50980
  async disconnectAllForScope(scopeId) {
50035
50981
  const suffix = `::${scopeId}`;
@@ -50039,10 +50985,28 @@ var init_mcp_client = __esm({
50039
50985
  }
50040
50986
  }
50041
50987
  }
50988
+ /**
50989
+ * Permanently disconnect and forget all scoped servers for a scope.
50990
+ * Used when an agent is removed and will never reconnect.
50991
+ */
50992
+ async removeAllForScope(scopeId) {
50993
+ const suffix = `::${scopeId}`;
50994
+ for (const key2 of [...this.servers.keys()]) {
50995
+ if (key2.endsWith(suffix)) {
50996
+ await this.disconnectServer(key2);
50997
+ }
50998
+ }
50999
+ for (const key2 of [...this.serverConfigs.keys()]) {
51000
+ if (key2.endsWith(suffix)) {
51001
+ this.serverConfigs.delete(key2);
51002
+ }
51003
+ }
51004
+ }
50042
51005
  async disconnectAll() {
50043
51006
  for (const name of [...this.servers.keys()]) {
50044
51007
  await this.disconnectServer(name);
50045
51008
  }
51009
+ this.serverConfigs.clear();
50046
51010
  }
50047
51011
  /**
50048
51012
  * Accumulates stdout data into a line buffer and dispatches
@@ -50532,71 +51496,178 @@ var init_browser_session = __esm({
50532
51496
  });
50533
51497
 
50534
51498
  // ../core/dist/tools/manager.js
50535
- function createBuilderTools(ctx) {
51499
+ function createPackageTools(ctx) {
50536
51500
  return [
50537
- ...ctx.listArtifacts ? [
50538
- {
50539
- name: "builder_list",
50540
- description: "List builder artifacts (custom-created or Hub-downloaded agent/team/skill packages). These can be installed with builder_install.",
50541
- inputSchema: {
50542
- type: "object",
50543
- properties: {
50544
- type: {
50545
- type: "string",
50546
- enum: ["agent", "team", "skill"],
50547
- description: "Filter by artifact type (optional)"
50548
- }
51501
+ {
51502
+ name: "package_list",
51503
+ description: 'List all available packages. type "agent": built-in roles (developer, content-writer, etc.) and custom agent packages. type "team": team templates (content-team, research-lab, etc.). type "skill": skill packages. Omit type to list all. Install with package_install. To find more online, use hub_search.',
51504
+ inputSchema: {
51505
+ type: "object",
51506
+ properties: {
51507
+ type: {
51508
+ type: "string",
51509
+ enum: ["agent", "team", "skill"],
51510
+ description: "Filter by type (optional). Omit to list all."
51511
+ }
51512
+ }
51513
+ },
51514
+ async execute(args) {
51515
+ const listArtifacts = ctx.listArtifacts?.();
51516
+ if (!listArtifacts)
51517
+ return JSON.stringify({ status: "error", error: "Package service is not available yet. Please try again later." });
51518
+ try {
51519
+ const type = args["type"];
51520
+ const artifacts = listArtifacts(type);
51521
+ const listTemplates = ctx.listTemplates?.();
51522
+ const roles = (!type || type === "agent") && listTemplates ? listTemplates() : [];
51523
+ const roleItems = roles.map((r) => ({
51524
+ type: "agent",
51525
+ source: "role",
51526
+ name: r.id ?? r.name,
51527
+ description: r.description ?? r.name,
51528
+ ...r
51529
+ }));
51530
+ const items = [...roleItems, ...artifacts];
51531
+ return JSON.stringify({ items, count: items.length });
51532
+ } catch (error) {
51533
+ return JSON.stringify({ status: "error", error: String(error) });
51534
+ }
51535
+ }
51536
+ },
51537
+ {
51538
+ name: "package_install",
51539
+ 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.',
51540
+ inputSchema: {
51541
+ type: "object",
51542
+ properties: {
51543
+ type: {
51544
+ type: "string",
51545
+ enum: ["agent", "team", "skill"],
51546
+ description: "Package type"
51547
+ },
51548
+ name: { type: "string", description: "Package name (from package_list)" },
51549
+ agent_name: { type: "string", description: 'Display name for the new agent (required when installing from a built-in role, e.g. "developer")' },
51550
+ skills: {
51551
+ type: "array",
51552
+ items: { type: "string" },
51553
+ description: "Optional skill IDs to assign to the new agent"
50549
51554
  }
50550
51555
  },
50551
- async execute(args) {
50552
- try {
50553
- const artifacts = ctx.listArtifacts(args["type"]);
50554
- return JSON.stringify({ artifacts, count: artifacts.length });
50555
- } catch (error) {
50556
- return JSON.stringify({ status: "error", error: String(error) });
51556
+ required: ["type", "name"]
51557
+ },
51558
+ async execute(args) {
51559
+ const installArtifact = ctx.installArtifact?.();
51560
+ if (!installArtifact)
51561
+ return JSON.stringify({ status: "error", error: "Package service is not available yet. Please try again later." });
51562
+ try {
51563
+ const type = args["type"]?.trim();
51564
+ const name = args["name"]?.trim();
51565
+ if (!type || !["agent", "team", "skill"].includes(type))
51566
+ return JSON.stringify({ status: "error", error: "type is required and must be one of: agent, team, skill" });
51567
+ if (!name)
51568
+ return JSON.stringify({ status: "error", error: "name is required \u2014 use package_list to see available packages" });
51569
+ if (type === "agent") {
51570
+ try {
51571
+ const result2 = await installArtifact(type, name);
51572
+ return JSON.stringify({
51573
+ status: "success",
51574
+ ...result2,
51575
+ next_steps: "Installed successfully. Next: onboard new agent with project context via agent_send_message, then assign tasks via task_create."
51576
+ });
51577
+ } catch {
51578
+ const hireFromTemplate = ctx.hireFromTemplate?.();
51579
+ if (hireFromTemplate) {
51580
+ const agentName = args["agent_name"]?.trim();
51581
+ if (!agentName)
51582
+ return JSON.stringify({ status: "error", error: "agent_name is required when installing from a built-in role \u2014 provide a display name for the new agent" });
51583
+ const result2 = await hireFromTemplate(name, agentName, args["skills"]);
51584
+ return JSON.stringify({
51585
+ status: "success",
51586
+ type: "agent",
51587
+ agent: result2,
51588
+ next_steps: "Agent hired and started. Next: onboard with project context via agent_send_message, then assign tasks via task_create."
51589
+ });
51590
+ }
51591
+ throw new Error(`Agent package not found: ${name}. Use package_list to see available packages.`);
51592
+ }
50557
51593
  }
51594
+ const result = await installArtifact(type, name);
51595
+ return JSON.stringify({
51596
+ status: "success",
51597
+ ...result,
51598
+ ...type === "team" ? {
51599
+ next_steps: "Installed successfully. Next: onboard new agent(s) with project context via agent_send_message, then assign initial tasks via task_create."
51600
+ } : {}
51601
+ });
51602
+ } catch (error) {
51603
+ return JSON.stringify({ status: "error", error: String(error) });
50558
51604
  }
50559
51605
  }
50560
- ] : [],
50561
- ...ctx.installArtifact ? [
50562
- {
50563
- name: "builder_install",
50564
- description: "Install a builder artifact \u2014 deploys an agent, team, or skill package into the live organization. For agents/teams: after installation, onboard with project context and assign initial tasks.",
50565
- inputSchema: {
50566
- type: "object",
50567
- properties: {
50568
- type: {
50569
- type: "string",
50570
- enum: ["agent", "team", "skill"],
50571
- description: "Artifact type"
50572
- },
50573
- name: { type: "string", description: "Artifact name (from builder_list)" }
51606
+ },
51607
+ {
51608
+ name: "hub_search",
51609
+ description: "Search Markus Hub for community-published agents, teams, and skills. Returns a list of available items with descriptions and popularity.",
51610
+ inputSchema: {
51611
+ type: "object",
51612
+ properties: {
51613
+ type: {
51614
+ type: "string",
51615
+ enum: ["agent", "team", "skill"],
51616
+ description: "Filter by item type (optional)"
50574
51617
  },
50575
- required: ["type", "name"]
50576
- },
50577
- async execute(args) {
50578
- try {
50579
- const type = args["type"]?.trim();
50580
- const name = args["name"]?.trim();
50581
- if (!type || !["agent", "team", "skill"].includes(type))
50582
- return JSON.stringify({ status: "error", error: "type is required and must be one of: agent, team, skill" });
50583
- if (!name)
50584
- return JSON.stringify({ status: "error", error: "name is required \u2014 provide the artifact name from builder_list" });
50585
- const result = await ctx.installArtifact(type, name);
50586
- const isAgentOrTeam = result.type === "agent" || result.type === "team";
50587
- return JSON.stringify({
50588
- status: "success",
50589
- ...result,
50590
- ...isAgentOrTeam ? {
50591
- next_steps: "Installed successfully. Next: onboard new agent(s) with project context via agent_send_message, then assign initial tasks via task_create."
50592
- } : {}
50593
- });
50594
- } catch (error) {
50595
- return JSON.stringify({ status: "error", error: String(error) });
51618
+ query: {
51619
+ type: "string",
51620
+ description: "Search query (optional)"
50596
51621
  }
50597
51622
  }
51623
+ },
51624
+ async execute(args) {
51625
+ const searchHub = ctx.searchHub?.();
51626
+ if (!searchHub)
51627
+ return JSON.stringify({ status: "error", error: "Hub service is not available. Please try again later." });
51628
+ try {
51629
+ const items = await searchHub({
51630
+ type: args["type"],
51631
+ query: args["query"]
51632
+ });
51633
+ return JSON.stringify({ items, count: items.length });
51634
+ } catch (error) {
51635
+ return JSON.stringify({ status: "error", error: String(error) });
51636
+ }
50598
51637
  }
50599
- ] : []
51638
+ },
51639
+ {
51640
+ name: "hub_install",
51641
+ 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.",
51642
+ inputSchema: {
51643
+ type: "object",
51644
+ properties: {
51645
+ item_id: { type: "string", description: "Hub item ID (from hub_search results)" }
51646
+ },
51647
+ required: ["item_id"]
51648
+ },
51649
+ async execute(args) {
51650
+ const downloadAndInstall = ctx.downloadAndInstall?.();
51651
+ if (!downloadAndInstall)
51652
+ return JSON.stringify({ status: "error", error: "Hub service is not available. Please try again later." });
51653
+ try {
51654
+ const itemId = args["item_id"]?.trim();
51655
+ if (!itemId)
51656
+ return JSON.stringify({ status: "error", error: "item_id is required \u2014 use hub_search to find available items first" });
51657
+ const result = await downloadAndInstall(itemId);
51658
+ const isAgentOrTeam = result.type === "agent" || result.type === "team";
51659
+ return JSON.stringify({
51660
+ status: "success",
51661
+ ...result,
51662
+ ...isAgentOrTeam ? {
51663
+ next_steps: "Downloaded and installed from Hub. Next: onboard new agent(s) with project context via agent_send_message, then assign initial tasks via task_create."
51664
+ } : {}
51665
+ });
51666
+ } catch (error) {
51667
+ return JSON.stringify({ status: "error", error: String(error) });
51668
+ }
51669
+ }
51670
+ }
50600
51671
  ];
50601
51672
  }
50602
51673
  function createManagerTools(ctx) {
@@ -50717,40 +51788,6 @@ function createManagerTools(ctx) {
50717
51788
  }
50718
51789
  }
50719
51790
  ] : [],
50720
- ...ctx.listArtifacts ? [
50721
- {
50722
- name: "package_list",
50723
- description: 'List all available packages. type "agent": built-in roles (developer, content-writer, etc.) and custom agent packages. type "team": team templates (content-team, research-lab, etc.). type "skill": skill packages. Omit type to list all. Install with package_install. To find more online, use hub_search.',
50724
- inputSchema: {
50725
- type: "object",
50726
- properties: {
50727
- type: {
50728
- type: "string",
50729
- enum: ["agent", "team", "skill"],
50730
- description: "Filter by type (optional). Omit to list all."
50731
- }
50732
- }
50733
- },
50734
- async execute(args) {
50735
- try {
50736
- const type = args["type"];
50737
- const artifacts = ctx.listArtifacts(type);
50738
- const roles = (!type || type === "agent") && ctx.listTemplates ? ctx.listTemplates() : [];
50739
- const roleItems = roles.map((r) => ({
50740
- type: "agent",
50741
- source: "role",
50742
- name: r.id ?? r.name,
50743
- description: r.description ?? r.name,
50744
- ...r
50745
- }));
50746
- const items = [...roleItems, ...artifacts];
50747
- return JSON.stringify({ items, count: items.length });
50748
- } catch (error) {
50749
- return JSON.stringify({ status: "error", error: String(error) });
50750
- }
50751
- }
50752
- }
50753
- ] : [],
50754
51791
  ...ctx.updateTeam ? [
50755
51792
  {
50756
51793
  name: "team_update",
@@ -50813,74 +51850,6 @@ function createManagerTools(ctx) {
50813
51850
  }
50814
51851
  }
50815
51852
  }
50816
- ] : [],
50817
- ...ctx.installArtifact ? [
50818
- {
50819
- name: "package_install",
50820
- 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.',
50821
- inputSchema: {
50822
- type: "object",
50823
- properties: {
50824
- type: {
50825
- type: "string",
50826
- enum: ["agent", "team", "skill"],
50827
- description: "Package type"
50828
- },
50829
- name: { type: "string", description: "Package name (from package_list)" },
50830
- agent_name: { type: "string", description: 'Display name for the new agent (required when installing from a built-in role, e.g. "developer")' },
50831
- skills: {
50832
- type: "array",
50833
- items: { type: "string" },
50834
- description: "Optional skill IDs to assign to the new agent"
50835
- }
50836
- },
50837
- required: ["type", "name"]
50838
- },
50839
- async execute(args) {
50840
- try {
50841
- const type = args["type"]?.trim();
50842
- const name = args["name"]?.trim();
50843
- if (!type || !["agent", "team", "skill"].includes(type))
50844
- return JSON.stringify({ status: "error", error: "type is required and must be one of: agent, team, skill" });
50845
- if (!name)
50846
- return JSON.stringify({ status: "error", error: "name is required \u2014 use package_list to see available packages" });
50847
- if (type === "agent") {
50848
- try {
50849
- const result2 = await ctx.installArtifact(type, name);
50850
- return JSON.stringify({
50851
- status: "success",
50852
- ...result2,
50853
- next_steps: "Installed successfully. Next: onboard new agent with project context via agent_send_message, then assign tasks via task_create."
50854
- });
50855
- } catch {
50856
- if (ctx.hireFromTemplate) {
50857
- const agentName = args["agent_name"]?.trim();
50858
- if (!agentName)
50859
- return JSON.stringify({ status: "error", error: "agent_name is required when installing from a built-in role \u2014 provide a display name for the new agent" });
50860
- const result2 = await ctx.hireFromTemplate(name, agentName, args["skills"]);
50861
- return JSON.stringify({
50862
- status: "success",
50863
- type: "agent",
50864
- agent: result2,
50865
- next_steps: "Agent hired and started. Next: onboard with project context via agent_send_message, then assign tasks via task_create."
50866
- });
50867
- }
50868
- throw new Error(`Agent package not found: ${name}. Use package_list to see available packages.`);
50869
- }
50870
- }
50871
- const result = await ctx.installArtifact(type, name);
50872
- return JSON.stringify({
50873
- status: "success",
50874
- ...result,
50875
- ...type === "team" ? {
50876
- next_steps: "Installed successfully. Next: onboard new agent(s) with project context via agent_send_message, then assign initial tasks via task_create."
50877
- } : {}
50878
- });
50879
- } catch (error) {
50880
- return JSON.stringify({ status: "error", error: String(error) });
50881
- }
50882
- }
50883
- }
50884
51853
  ] : []
50885
51854
  ];
50886
51855
  }
@@ -50893,75 +51862,6 @@ var init_manager = __esm({
50893
51862
  }
50894
51863
  });
50895
51864
 
50896
- // ../core/dist/tools/hub-tools.js
50897
- function createHubTools(ctx) {
50898
- return [
50899
- {
50900
- name: "hub_search",
50901
- description: "Search Markus Hub for community-published agents, teams, and skills. Returns a list of available items with descriptions and popularity.",
50902
- inputSchema: {
50903
- type: "object",
50904
- properties: {
50905
- type: {
50906
- type: "string",
50907
- enum: ["agent", "team", "skill"],
50908
- description: "Filter by item type (optional)"
50909
- },
50910
- query: {
50911
- type: "string",
50912
- description: "Search query (optional)"
50913
- }
50914
- }
50915
- },
50916
- async execute(args) {
50917
- try {
50918
- const items = await ctx.searchHub({
50919
- type: args["type"],
50920
- query: args["query"]
50921
- });
50922
- return JSON.stringify({ items, count: items.length });
50923
- } catch (error) {
50924
- return JSON.stringify({ status: "error", error: String(error) });
50925
- }
50926
- }
50927
- },
50928
- {
50929
- name: "hub_install",
50930
- 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.",
50931
- inputSchema: {
50932
- type: "object",
50933
- properties: {
50934
- item_id: { type: "string", description: "Hub item ID (from hub_search results)" }
50935
- },
50936
- required: ["item_id"]
50937
- },
50938
- async execute(args) {
50939
- try {
50940
- const itemId = args["item_id"]?.trim();
50941
- if (!itemId)
50942
- return JSON.stringify({ status: "error", error: "item_id is required \u2014 use hub_search to find available items first" });
50943
- const result = await ctx.downloadAndInstall(itemId);
50944
- const isAgentOrTeam = result.type === "agent" || result.type === "team";
50945
- return JSON.stringify({
50946
- status: "success",
50947
- ...result,
50948
- ...isAgentOrTeam ? {
50949
- next_steps: "Downloaded and installed from Hub. Next: onboard new agent(s) with project context via agent_send_message, then assign initial tasks via task_create."
50950
- } : {}
50951
- });
50952
- } catch (error) {
50953
- return JSON.stringify({ status: "error", error: String(error) });
50954
- }
50955
- }
50956
- }
50957
- ];
50958
- }
50959
- var init_hub_tools = __esm({
50960
- "../core/dist/tools/hub-tools.js"() {
50961
- "use strict";
50962
- }
50963
- });
50964
-
50965
51865
  // ../core/dist/tools/a2a.js
50966
51866
  function createA2ATools(ctx) {
50967
51867
  return [
@@ -52722,6 +53622,10 @@ function createProjectTools(ctx) {
52722
53622
  type: "string",
52723
53623
  description: "Path to the file or directory that contains the actual content (must already exist)"
52724
53624
  },
53625
+ format: {
53626
+ type: "string",
53627
+ description: "Content format of the deliverable file: markdown, html, text, json, csv, etc. Auto-detected from file extension if omitted."
53628
+ },
52725
53629
  tags: { type: "string", description: "Comma-separated tags for discoverability" }
52726
53630
  },
52727
53631
  required: ["type", "title", "summary"]
@@ -52733,6 +53637,7 @@ function createProjectTools(ctx) {
52733
53637
  title: args["title"],
52734
53638
  summary: args["summary"],
52735
53639
  reference: args["reference"],
53640
+ format: args["format"],
52736
53641
  tags: args["tags"]
52737
53642
  });
52738
53643
  const resp = {
@@ -53980,7 +54885,6 @@ var init_agent_manager = __esm({
53980
54885
  init_mcp_client();
53981
54886
  init_browser_session();
53982
54887
  init_manager();
53983
- init_hub_tools();
53984
54888
  init_a2a();
53985
54889
  init_a2a_structured();
53986
54890
  init_task_tools();
@@ -54020,6 +54924,9 @@ var init_agent_manager = __esm({
54020
54924
  escalationHandler;
54021
54925
  approvalHandler;
54022
54926
  stateChangeHandler;
54927
+ /** Grace timers for releasing scoped MCP processes after agent goes idle */
54928
+ mcpReleaseTimers = /* @__PURE__ */ new Map();
54929
+ static MCP_IDLE_GRACE_MS = 3e4;
54023
54930
  activityCallbacks;
54024
54931
  recallCallbacks;
54025
54932
  delegationManager;
@@ -54086,6 +54993,7 @@ var init_agent_manager = __esm({
54086
54993
  title: opts.title,
54087
54994
  summary: opts.summary,
54088
54995
  reference: opts.reference,
54996
+ format: opts.format,
54089
54997
  tags,
54090
54998
  agentId: agentId2,
54091
54999
  projectId
@@ -54808,26 +55716,6 @@ Known issues: ${knownIssues}` : ""}`
54808
55716
  findDuplicateTasks: this.taskService?.findDuplicateTasks ? (orgId2) => this.taskService.findDuplicateTasks(orgId2) : void 0,
54809
55717
  cleanupDuplicateTasks: this.taskService?.cleanupDuplicateTasks ? (orgId2) => this.taskService.cleanupDuplicateTasks(orgId2) : void 0,
54810
55718
  getTaskBoardHealth: this.taskService?.getTaskBoardHealth ? (orgId2) => this.taskService.getTaskBoardHealth(orgId2) : void 0,
54811
- hireFromTemplate: this.templateRegistry ? async (templateId, name, skills) => {
54812
- const newAgent = await this.createAgentFromTemplate({
54813
- templateId,
54814
- name,
54815
- orgId: config.orgId ?? "default",
54816
- teamId: config.teamId,
54817
- overrides: skills ? { skills } : void 0
54818
- });
54819
- await this.startAgent(newAgent.id);
54820
- return { id: newAgent.id, name: newAgent.config.name, role: newAgent.role.name };
54821
- } : void 0,
54822
- listTemplates: this.templateRegistry ? () => this.templateRegistry.list().map((t) => ({
54823
- id: t.id,
54824
- name: t.name,
54825
- description: t.description,
54826
- roleId: t.roleId,
54827
- category: t.category ?? "general"
54828
- })) : void 0,
54829
- installArtifact: this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
54830
- listArtifacts: this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
54831
55719
  updateTeam: this.teamUpdater ? async (teamId, data) => this.teamUpdater(teamId, data) : void 0,
54832
55720
  updateAgentConfig: async (agentId2, data) => {
54833
55721
  const targetAgent = this.getAgent(agentId2);
@@ -54843,23 +55731,33 @@ Known issues: ${knownIssues}` : ""}`
54843
55731
  for (const tool of managerTools) {
54844
55732
  agent.registerTool(tool);
54845
55733
  }
54846
- const BUILDING_SKILLS = /* @__PURE__ */ new Set(["agent-building", "team-building", "skill-building"]);
54847
- const hasBuilderSkill = config.skills?.some((s2) => BUILDING_SKILLS.has(s2));
54848
- if (hasBuilderSkill && this.hubClient) {
54849
- const hubTools = createHubTools({
54850
- searchHub: (opts) => this.hubClient.search(opts),
54851
- downloadAndInstall: (itemId) => this.hubClient.downloadAndInstall(itemId)
54852
- });
54853
- for (const tool of hubTools)
54854
- agent.registerTool(tool);
54855
- }
54856
55734
  }
54857
- if (this.builderService) {
54858
- const builderTools = createBuilderTools({
54859
- installArtifact: (type, name) => this.builderService.installArtifact(type, name),
54860
- listArtifacts: (type) => this.builderService.listArtifacts(type)
55735
+ {
55736
+ const packageTools = createPackageTools({
55737
+ installArtifact: () => this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
55738
+ listArtifacts: () => this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
55739
+ hireFromTemplate: () => this.templateRegistry ? async (templateId, name, skills) => {
55740
+ const newAgent = await this.createAgentFromTemplate({
55741
+ templateId,
55742
+ name,
55743
+ orgId: config.orgId ?? "default",
55744
+ teamId: config.teamId,
55745
+ overrides: skills ? { skills } : void 0
55746
+ });
55747
+ await this.startAgent(newAgent.id);
55748
+ return { id: newAgent.id, name: newAgent.config.name, role: newAgent.role.name };
55749
+ } : void 0,
55750
+ listTemplates: () => this.templateRegistry ? () => this.templateRegistry.list().map((t) => ({
55751
+ id: t.id,
55752
+ name: t.name,
55753
+ description: t.description,
55754
+ roleId: t.roleId,
55755
+ category: t.category ?? "general"
55756
+ })) : void 0,
55757
+ searchHub: () => this.hubClient ? (opts) => this.hubClient.search(opts) : void 0,
55758
+ downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0
54861
55759
  });
54862
- for (const tool of builderTools)
55760
+ for (const tool of packageTools)
54863
55761
  agent.registerTool(tool);
54864
55762
  }
54865
55763
  const mcpConfigs = request.mcpServers ?? this.globalMcpServers;
@@ -54892,9 +55790,7 @@ Known issues: ${knownIssues}` : ""}`
54892
55790
  const ah = this.approvalHandler;
54893
55791
  agent.setApprovalCallback(async (req) => ah(id, req));
54894
55792
  }
54895
- if (this.stateChangeHandler) {
54896
- agent.setStateChangeCallback(this.stateChangeHandler);
54897
- }
55793
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
54898
55794
  if (this.activityCallbacks) {
54899
55795
  agent.setActivityCallbacks(this.activityCallbacks);
54900
55796
  }
@@ -55389,7 +56285,26 @@ Known issues: ${knownIssues}` : ""}`
55389
56285
  findDuplicateTasks: this.taskService?.findDuplicateTasks ? (orgId2) => this.taskService.findDuplicateTasks(orgId2) : void 0,
55390
56286
  cleanupDuplicateTasks: this.taskService?.cleanupDuplicateTasks ? (orgId2) => this.taskService.cleanupDuplicateTasks(orgId2) : void 0,
55391
56287
  getTaskBoardHealth: this.taskService?.getTaskBoardHealth ? (orgId2) => this.taskService.getTaskBoardHealth(orgId2) : void 0,
55392
- hireFromTemplate: this.templateRegistry ? async (templateId, name, skills) => {
56288
+ updateTeam: this.teamUpdater ? async (teamId, data) => this.teamUpdater(teamId, data) : void 0,
56289
+ updateAgentConfig: async (agentId2, data) => {
56290
+ const targetAgent = this.getAgent(agentId2);
56291
+ if (data.name !== void 0) {
56292
+ targetAgent.config.name = data.name;
56293
+ }
56294
+ if (this.agentConfigPersister) {
56295
+ await this.agentConfigPersister(agentId2, data);
56296
+ }
56297
+ return { id: agentId2, name: targetAgent.config.name };
56298
+ }
56299
+ });
56300
+ for (const tool of managerTools)
56301
+ agent.registerTool(tool);
56302
+ }
56303
+ {
56304
+ const packageTools = createPackageTools({
56305
+ installArtifact: () => this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
56306
+ listArtifacts: () => this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
56307
+ hireFromTemplate: () => this.templateRegistry ? async (templateId, name, skills) => {
55393
56308
  const newAgent = await this.createAgentFromTemplate({
55394
56309
  templateId,
55395
56310
  name,
@@ -55400,46 +56315,17 @@ Known issues: ${knownIssues}` : ""}`
55400
56315
  await this.startAgent(newAgent.id);
55401
56316
  return { id: newAgent.id, name: newAgent.config.name, role: newAgent.role.name };
55402
56317
  } : void 0,
55403
- listTemplates: this.templateRegistry ? () => this.templateRegistry.list().map((t) => ({
56318
+ listTemplates: () => this.templateRegistry ? () => this.templateRegistry.list().map((t) => ({
55404
56319
  id: t.id,
55405
56320
  name: t.name,
55406
56321
  description: t.description,
55407
56322
  roleId: t.roleId,
55408
56323
  category: t.category ?? "general"
55409
56324
  })) : void 0,
55410
- installArtifact: this.builderService ? (type, name) => this.builderService.installArtifact(type, name) : void 0,
55411
- listArtifacts: this.builderService ? (type) => this.builderService.listArtifacts(type) : void 0,
55412
- updateTeam: this.teamUpdater ? async (teamId, data) => this.teamUpdater(teamId, data) : void 0,
55413
- updateAgentConfig: async (agentId2, data) => {
55414
- const targetAgent = this.getAgent(agentId2);
55415
- if (data.name !== void 0) {
55416
- targetAgent.config.name = data.name;
55417
- }
55418
- if (this.agentConfigPersister) {
55419
- await this.agentConfigPersister(agentId2, data);
55420
- }
55421
- return { id: agentId2, name: targetAgent.config.name };
55422
- }
56325
+ searchHub: () => this.hubClient ? (opts) => this.hubClient.search(opts) : void 0,
56326
+ downloadAndInstall: () => this.hubClient ? (itemId) => this.hubClient.downloadAndInstall(itemId) : void 0
55423
56327
  });
55424
- for (const tool of managerTools)
55425
- agent.registerTool(tool);
55426
- const BUILDING_SKILLS_R = /* @__PURE__ */ new Set(["agent-building", "team-building", "skill-building"]);
55427
- const hasBuilderSkillR = config.skills?.some((s2) => BUILDING_SKILLS_R.has(s2));
55428
- if (hasBuilderSkillR && this.hubClient) {
55429
- const hubTools = createHubTools({
55430
- searchHub: (opts) => this.hubClient.search(opts),
55431
- downloadAndInstall: (itemId) => this.hubClient.downloadAndInstall(itemId)
55432
- });
55433
- for (const tool of hubTools)
55434
- agent.registerTool(tool);
55435
- }
55436
- }
55437
- if (this.builderService) {
55438
- const builderTools = createBuilderTools({
55439
- installArtifact: (type, name) => this.builderService.installArtifact(type, name),
55440
- listArtifacts: (type) => this.builderService.listArtifacts(type)
55441
- });
55442
- for (const tool of builderTools)
56328
+ for (const tool of packageTools)
55443
56329
  agent.registerTool(tool);
55444
56330
  }
55445
56331
  if (this.agentAuditCallback) {
@@ -55453,9 +56339,7 @@ Known issues: ${knownIssues}` : ""}`
55453
56339
  const ah = this.approvalHandler;
55454
56340
  agent.setApprovalCallback(async (req) => ah(id, req));
55455
56341
  }
55456
- if (this.stateChangeHandler) {
55457
- agent.setStateChangeCallback(this.stateChangeHandler);
55458
- }
56342
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
55459
56343
  if (this.activityCallbacks) {
55460
56344
  agent.setActivityCallbacks(this.activityCallbacks);
55461
56345
  }
@@ -55519,6 +56403,9 @@ Known issues: ${knownIssues}` : ""}`
55519
56403
  async stopAgent(agentId2) {
55520
56404
  const agent = this.getAgent(agentId2);
55521
56405
  await agent.stop();
56406
+ this.cancelMcpRelease(agentId2);
56407
+ this.browserSessionManager.cleanupAgent(agentId2);
56408
+ await this.mcpManager.removeAllForScope(agentId2);
55522
56409
  }
55523
56410
  async removeAgent(agentId2, opts) {
55524
56411
  const agent = this.agents.get(agentId2);
@@ -55527,8 +56414,9 @@ Known issues: ${knownIssues}` : ""}`
55527
56414
  await agent.stop();
55528
56415
  } catch {
55529
56416
  }
56417
+ this.cancelMcpRelease(agentId2);
55530
56418
  this.browserSessionManager.cleanupAgent(agentId2);
55531
- await this.mcpManager.disconnectAllForScope(agentId2);
56419
+ await this.mcpManager.removeAllForScope(agentId2);
55532
56420
  this.delegationManager.unregisterAgentCard(agentId2);
55533
56421
  this.agents.delete(agentId2);
55534
56422
  this.eventBus.emit("agent:removed", { agentId: agentId2 });
@@ -55596,13 +56484,49 @@ Known issues: ${knownIssues}` : ""}`
55596
56484
  agent.setApprovalCallback(async (req) => handler4(id, req));
55597
56485
  }
55598
56486
  }
55599
- setStateChangeHandler(handler4) {
55600
- this.stateChangeHandler = (agentId2, state) => {
55601
- handler4(agentId2, state);
56487
+ /**
56488
+ * Build the combined state-change callback for an agent. Handles:
56489
+ * 1. MCP scoped-process lifecycle (release on idle, cancel release on working)
56490
+ * 2. DelegationManager status sync
56491
+ * 3. External handler forwarding (DB persistence, WS broadcast, etc.)
56492
+ */
56493
+ buildStateChangeCallback() {
56494
+ return (agentId2, state) => {
56495
+ if (state.status === "idle" && state.activeTaskIds.length === 0) {
56496
+ this.scheduleMcpRelease(agentId2);
56497
+ } else if (state.status === "working") {
56498
+ this.cancelMcpRelease(agentId2);
56499
+ }
55602
56500
  this.delegationManager.updateAgentStatus(agentId2, state.status);
56501
+ if (this.stateChangeHandler) {
56502
+ this.stateChangeHandler(agentId2, state);
56503
+ }
55603
56504
  };
56505
+ }
56506
+ scheduleMcpRelease(agentId2) {
56507
+ if (this.mcpReleaseTimers.has(agentId2))
56508
+ return;
56509
+ const timer = setTimeout(() => {
56510
+ this.mcpReleaseTimers.delete(agentId2);
56511
+ this.browserSessionManager.cleanupAgent(agentId2);
56512
+ this.mcpManager.disconnectAllForScope(agentId2).catch(() => {
56513
+ });
56514
+ log32.info(`Released scoped MCP processes for idle agent: ${agentId2}`);
56515
+ }, _AgentManager.MCP_IDLE_GRACE_MS);
56516
+ timer.unref();
56517
+ this.mcpReleaseTimers.set(agentId2, timer);
56518
+ }
56519
+ cancelMcpRelease(agentId2) {
56520
+ const timer = this.mcpReleaseTimers.get(agentId2);
56521
+ if (timer) {
56522
+ clearTimeout(timer);
56523
+ this.mcpReleaseTimers.delete(agentId2);
56524
+ }
56525
+ }
56526
+ setStateChangeHandler(handler4) {
56527
+ this.stateChangeHandler = handler4;
55604
56528
  for (const [, agent] of this.agents) {
55605
- agent.setStateChangeCallback(this.stateChangeHandler);
56529
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
55606
56530
  }
55607
56531
  }
55608
56532
  /**
@@ -55623,6 +56547,10 @@ Known issues: ${knownIssues}` : ""}`
55623
56547
  "agent:resumed",
55624
56548
  "agent:focus-changed",
55625
56549
  "agent:message",
56550
+ "agent:notify-user",
56551
+ "agent:escalation",
56552
+ "agent:activity-log",
56553
+ "agent:activity_log",
55626
56554
  "task:completed",
55627
56555
  "task:failed",
55628
56556
  "mailbox:new-item",
@@ -55860,6 +56788,7 @@ Known issues: ${knownIssues}` : ""}`
55860
56788
  } catch {
55861
56789
  }
55862
56790
  }
56791
+ await this.mcpManager.disconnectAll();
55863
56792
  log32.info("AgentManager shutdown complete \u2014 all metrics flushed");
55864
56793
  }
55865
56794
  // ─── Role Template Versioning & Sync ──────────────────────────────────────
@@ -56277,7 +57206,7 @@ var init_anthropic = __esm({
56277
57206
  {
56278
57207
  type: "tool_result",
56279
57208
  tool_use_id: m.toolCallId ?? "",
56280
- content: getTextContent(m.content)
57209
+ content: sanitizeForLLM(getTextContent(m.content))
56281
57210
  }
56282
57211
  ]
56283
57212
  };
@@ -56470,7 +57399,7 @@ var init_openai = __esm({
56470
57399
  if (m.role === "tool") {
56471
57400
  return {
56472
57401
  role: "tool",
56473
- content: getTextContent(m.content),
57402
+ content: sanitizeForLLM(getTextContent(m.content)),
56474
57403
  tool_call_id: m.toolCallId ?? ""
56475
57404
  };
56476
57405
  }
@@ -56874,7 +57803,7 @@ var init_google = __esm({
56874
57803
  parts: [{
56875
57804
  functionResponse: {
56876
57805
  name: msg.toolCallId ?? "unknown",
56877
- response: { result: getTextContent(msg.content) }
57806
+ response: { result: sanitizeForLLM(getTextContent(msg.content)) }
56878
57807
  }
56879
57808
  }]
56880
57809
  });
@@ -57096,7 +58025,7 @@ var init_ollama = __esm({
57096
58025
  convertMessages(messages) {
57097
58026
  return messages.map((m) => {
57098
58027
  if (m.role === "tool") {
57099
- return { role: "tool", content: getTextContent(m.content) };
58028
+ return { role: "tool", content: sanitizeForLLM(getTextContent(m.content)) };
57100
58029
  }
57101
58030
  if (m.toolCalls?.length) {
57102
58031
  return {
@@ -57332,9 +58261,9 @@ var init_auth_profiles = __esm({
57332
58261
 
57333
58262
  // ../core/dist/llm/oauth-manager.js
57334
58263
  import { createServer } from "node:http";
57335
- import { randomBytes as randomBytes2, createHash } from "node:crypto";
58264
+ import { randomBytes as randomBytes3, createHash } from "node:crypto";
57336
58265
  function generatePKCE() {
57337
- const verifier = randomBytes2(32).toString("base64url");
58266
+ const verifier = randomBytes3(32).toString("base64url");
57338
58267
  const challenge = createHash("sha256").update(verifier).digest("base64url");
57339
58268
  return { verifier, challenge };
57340
58269
  }
@@ -57415,7 +58344,7 @@ var init_oauth_manager = __esm({
57415
58344
  throw new Error(`Unknown OAuth provider: ${provider}`);
57416
58345
  this.cancelPendingLogin(provider);
57417
58346
  const { verifier, challenge } = generatePKCE();
57418
- const state = randomBytes2(16).toString("hex");
58347
+ const state = randomBytes3(16).toString("hex");
57419
58348
  const port = config.callbackPort ?? 1455;
57420
58349
  const redirectUri = `http://localhost:${port}/auth/callback`;
57421
58350
  const promise = new Promise((resolve20, reject) => {
@@ -57704,6 +58633,7 @@ var init_router = __esm({
57704
58633
  autoSelect = false;
57705
58634
  providerTiers = [];
57706
58635
  fallbackOrder = [];
58636
+ _autoFallback = true;
57707
58637
  /** Health tracked per model: key = "providerName:modelId" */
57708
58638
  modelHealth = /* @__PURE__ */ new Map();
57709
58639
  /** Provider-level degradation for non-retryable (auth/billing) errors */
@@ -57992,6 +58922,12 @@ var init_router = __esm({
57992
58922
  setFallbackOrder(order) {
57993
58923
  this.fallbackOrder = order.filter((n2) => this.providers.has(n2));
57994
58924
  }
58925
+ get autoFallback() {
58926
+ return this._autoFallback;
58927
+ }
58928
+ setAutoFallback(enabled) {
58929
+ this._autoFallback = enabled;
58930
+ }
57995
58931
  static assessComplexity(request) {
57996
58932
  const totalChars = request.messages.reduce((s2, m) => s2 + getTextContent(m.content).length, 0);
57997
58933
  const toolCount = request.tools?.length ?? 0;
@@ -58043,6 +58979,8 @@ var init_router = __esm({
58043
58979
  return this.defaultProvider;
58044
58980
  }
58045
58981
  getFallbacks(primary) {
58982
+ if (!this._autoFallback)
58983
+ return [];
58046
58984
  const order = this.fallbackOrder.length > 0 ? this.fallbackOrder : [...this.providers.keys()];
58047
58985
  return order.filter((n2) => n2 !== primary && this.isAvailable(n2));
58048
58986
  }
@@ -58161,7 +59099,7 @@ var init_router = __esm({
58161
59099
  } catch (error) {
58162
59100
  lastError = error;
58163
59101
  log35.error(`LLM request failed for ${primary}:${provider.model}`, { error: String(error) });
58164
- if (!_LLMRouter.isNonRetryableError(error)) {
59102
+ if (this._autoFallback && !_LLMRouter.isNonRetryableError(error)) {
58165
59103
  const altModel = this.findHealthyModel(primary);
58166
59104
  if (altModel && altModel !== provider.model) {
58167
59105
  log35.info(`Trying alternate model ${altModel} on ${primary}`);
@@ -58252,7 +59190,7 @@ var init_router = __esm({
58252
59190
  throw lastError;
58253
59191
  }
58254
59192
  log35.error(`LLM stream request failed for ${primary}:${provider.model}`, { error: String(error) });
58255
- if (!_LLMRouter.isNonRetryableError(error)) {
59193
+ if (this._autoFallback && !_LLMRouter.isNonRetryableError(error)) {
58256
59194
  const altModel = this.findHealthyModel(primary);
58257
59195
  if (altModel && altModel !== provider.model) {
58258
59196
  log35.info(`Stream: trying alternate model ${altModel} on ${primary}`);
@@ -58383,7 +59321,7 @@ var init_router = __esm({
58383
59321
  };
58384
59322
  }
58385
59323
  }
58386
- return { defaultProvider: this.defaultProvider, providers };
59324
+ return { defaultProvider: this.defaultProvider, autoFallback: this._autoFallback, providers };
58387
59325
  }
58388
59326
  updateProviderModelConfig(providerName, config) {
58389
59327
  this.customModelConfigs.set(providerName, {
@@ -58480,6 +59418,17 @@ var init_router = __esm({
58480
59418
  const provider = this.providers.get(name);
58481
59419
  return provider?.model ?? "";
58482
59420
  }
59421
+ getModelCost(providerName) {
59422
+ const name = providerName ?? this.defaultProvider;
59423
+ const provider = this.providers.get(name);
59424
+ if (!provider)
59425
+ return void 0;
59426
+ const custom = this.customModelConfigs.get(name);
59427
+ if (custom?.cost)
59428
+ return custom.cost;
59429
+ const catalogEntry = BUILTIN_MODEL_CATALOG.find((m) => m.id === provider.model && m.provider === name) ?? BUILTIN_MODEL_CATALOG.find((m) => m.id === provider.model) ?? this.customModelCatalog.get(name)?.find((m) => m.id === provider.model);
59430
+ return catalogEntry?.cost;
59431
+ }
58483
59432
  getModelInputTypes(providerName) {
58484
59433
  const name = providerName ?? this.defaultProvider;
58485
59434
  const provider = this.providers.get(name);
@@ -58638,7 +59587,7 @@ var init_enhanced_role_loader = __esm({
58638
59587
  });
58639
59588
 
58640
59589
  // ../core/dist/external-gateway.js
58641
- import { createHmac, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
59590
+ import { createHmac, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
58642
59591
  var log37, ExternalAgentGateway, GatewayError;
58643
59592
  var init_external_gateway = __esm({
58644
59593
  "../core/dist/external-gateway.js"() {
@@ -58817,7 +59766,7 @@ var init_external_gateway = __esm({
58817
59766
  return payload;
58818
59767
  }
58819
59768
  async routeMessage(token, message) {
58820
- const messageId = `gwmsg_${Date.now()}_${randomBytes3(4).toString("hex")}`;
59769
+ const messageId = `gwmsg_${Date.now()}_${randomBytes4(4).toString("hex")}`;
58821
59770
  const key2 = this.registrationKey(token.externalAgentId, token.orgId);
58822
59771
  const registration = this.registrations.get(key2);
58823
59772
  if (registration) {
@@ -59334,7 +60283,6 @@ var init_tools = __esm({
59334
60283
  init_memory();
59335
60284
  init_subagent();
59336
60285
  init_settings();
59337
- init_hub_tools();
59338
60286
  init_recall();
59339
60287
  }
59340
60288
  });
@@ -61432,39 +62380,28 @@ var init_org_service = __esm({
61432
62380
  return p;
61433
62381
  }
61434
62382
  /**
61435
- * Seed a default team ("My Team") for a fresh organization.
61436
- * Creates the team, adds the owner as a human member, and hires a Secretary agent
61437
- * as the team manager. Skips if any teams already exist for the org.
62383
+ * Seed a Secretary agent for a fresh organization.
62384
+ * The Secretary is hired without a team users create teams explicitly.
62385
+ * Skips if any agents already exist for the org.
61438
62386
  */
61439
- async seedDefaultTeam(orgId2, ownerUserId) {
61440
- const existing = this.listTeams(orgId2);
61441
- if (existing.length > 0)
62387
+ async seedDefaultTeam(orgId2, _ownerUserId) {
62388
+ const existingAgents = this.agentManager.listAgents();
62389
+ if (existingAgents.length > 0)
61442
62390
  return;
61443
- log49.info("Seeding default team for org", { orgId: orgId2 });
62391
+ log49.info("Seeding Secretary agent for org", { orgId: orgId2 });
61444
62392
  try {
61445
- const team = await this.createTeam(orgId2, "My Team", "Your primary team \u2014 you and your personal AI Secretary.");
61446
- const owner = this.humans.get(ownerUserId);
61447
- if (owner) {
61448
- this.addMemberToTeam(team.id, ownerUserId, "human");
61449
- }
61450
62393
  const secretary = await this.hireAgent({
61451
62394
  name: "Secretary",
61452
62395
  roleName: "secretary",
61453
62396
  orgId: orgId2,
61454
- teamId: team.id,
61455
- agentRole: "manager",
62397
+ agentRole: "worker",
61456
62398
  skills: ["agent-building", "team-building", "skill-building"]
61457
62399
  });
61458
- await this.updateTeam(team.id, {
61459
- managerId: secretary.id,
61460
- managerType: "agent"
61461
- });
61462
- log49.info("Default team seeded", {
61463
- teamId: team.id,
62400
+ log49.info("Secretary agent seeded", {
61464
62401
  secretaryId: secretary.id
61465
62402
  });
61466
62403
  } catch (error) {
61467
- log49.warn("Failed to seed default team", { error: String(error) });
62404
+ log49.warn("Failed to seed Secretary agent", { error: String(error) });
61468
62405
  }
61469
62406
  }
61470
62407
  static BUILDING_SKILLS = /* @__PURE__ */ new Set(["agent-building", "team-building", "skill-building"]);
@@ -70555,6 +71492,10 @@ var init_task_service = __esm({
70555
71492
  taskCancelTokens = /* @__PURE__ */ new Map();
70556
71493
  /** Tasks currently being reviewed — prevents duplicate review notifications */
70557
71494
  activeReviews = /* @__PURE__ */ new Set();
71495
+ /** Tracks tasks where submitForReview was called in the current execution round.
71496
+ * Used to avoid false "did not call task_submit_review" when a fast reviewer
71497
+ * transitions the task back to in_progress before execution_finished fires. */
71498
+ tasksSubmittedForReview = /* @__PURE__ */ new Set();
70558
71499
  webhooks = [];
70559
71500
  timeoutCheckInterval;
70560
71501
  projectService;
@@ -71588,7 +72529,12 @@ ${c.content}`;
71588
72529
  if (entry.content === "completed" || entry.content === "execution_finished") {
71589
72530
  const currentTask = this.tasks.get(taskId2);
71590
72531
  const alreadyTerminal = currentTask && ["review", "completed", "failed", "cancelled", "archived"].includes(currentTask.status);
71591
- if (!alreadyTerminal && currentTask && currentTask.status === "in_progress") {
72532
+ const didSubmit = this.tasksSubmittedForReview.has(taskId2);
72533
+ if (didSubmit) {
72534
+ this.tasksSubmittedForReview.delete(taskId2);
72535
+ log50.info("Task execution finished after submitForReview \u2014 skipping no-submit retry (reviewer may have transitioned status back)", { taskId: taskId2 });
72536
+ }
72537
+ if (!alreadyTerminal && !didSubmit && currentTask && currentTask.status === "in_progress") {
71592
72538
  const nextAttempt = _retryAttempt + 1;
71593
72539
  if (nextAttempt < _TaskService.MAX_IN_PROGRESS_RETRIES) {
71594
72540
  const delayMs = withJitter(_TaskService.RETRY_DELAYS_MS[Math.min(_retryAttempt, _TaskService.RETRY_DELAYS_MS.length - 1)] ?? 3e5);
@@ -72201,6 +73147,9 @@ ${c.content}`;
72201
73147
  if (from === "review" && to !== "review") {
72202
73148
  this.activeReviews.delete(taskId2);
72203
73149
  }
73150
+ if (TERMINAL_STATUSES.has(to)) {
73151
+ this.tasksSubmittedForReview.delete(taskId2);
73152
+ }
72204
73153
  if (TERMINAL_STATUSES.has(to) && this.hitlService) {
72205
73154
  const cancelled = this.hitlService.cancelApprovalsByDetail("taskId", taskId2, "system", `Task ${to}`);
72206
73155
  if (cancelled > 0) {
@@ -72270,6 +73219,9 @@ ${deliverablesSummary}` : ""
72270
73219
  ],
72271
73220
  allowFreeform: true
72272
73221
  }).then((result) => {
73222
+ const current = this.tasks.get(task.id);
73223
+ if (!current || current.status !== "review")
73224
+ return;
72273
73225
  if (result.approved) {
72274
73226
  this.updateTaskStatus(task.id, "completed", task.reviewerId, false, false, "human", "Review approved");
72275
73227
  } else {
@@ -72538,6 +73490,7 @@ Action: ${guidance}` : ""
72538
73490
  };
72539
73491
  for (const t of tasks)
72540
73492
  statusCounts[t.status]++;
73493
+ const stuckBlockedCount = tasks.filter((t) => t.status === "blocked" && this.areBlockersSatisfied(t)).length;
72541
73494
  const agentMap = /* @__PURE__ */ new Map();
72542
73495
  for (const t of tasks) {
72543
73496
  if (!t.assignedAgentId)
@@ -72558,7 +73511,7 @@ Action: ${guidance}` : ""
72558
73511
  }
72559
73512
  return { agentId: agentId2, agentName, activeTasks: counts.active, completedTasks: counts.completed };
72560
73513
  });
72561
- const recentActivity = [...tasks].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()).slice(0, 20).map((t) => ({ taskId: t.id, title: t.title, status: t.status, updatedAt: t.updatedAt }));
73514
+ const recentActivity = [...tasks].filter((t) => t.status !== "archived").sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()).slice(0, 20).map((t) => ({ taskId: t.id, title: t.title, status: t.status, updatedAt: t.updatedAt }));
72562
73515
  const completedTasks = tasks.filter((t) => t.status === "completed" && t.updatedAt && t.createdAt);
72563
73516
  let averageCompletionTimeMs = null;
72564
73517
  if (completedTasks.length > 0) {
@@ -72570,6 +73523,7 @@ Action: ${guidance}` : ""
72570
73523
  return {
72571
73524
  statusCounts,
72572
73525
  totalTasks: tasks.length,
73526
+ stuckBlockedCount,
72573
73527
  agentWorkload,
72574
73528
  recentActivity,
72575
73529
  averageCompletionTimeMs
@@ -73010,6 +73964,7 @@ Action: ${guidance}` : ""
73010
73964
  success: true,
73011
73965
  metadata: { deliverableCount: deliverables.length, reviewReportStatus: reviewReport?.overallStatus }
73012
73966
  });
73967
+ this.tasksSubmittedForReview.add(task.id);
73013
73968
  this.updateTaskStatus(task.id, "review", task.assignedAgentId, false, false, "agent", "Submitted for review");
73014
73969
  log50.info(`Task submitted for review: ${task.title}`, { id: task.id });
73015
73970
  return task;
@@ -73826,7 +74781,12 @@ ${task.description}`;
73826
74781
  if (entry.type === "status" && (entry.content === "execution_finished" || entry.content === "completed")) {
73827
74782
  const currentTask = this.tasks.get(taskId2);
73828
74783
  const alreadyTerminal = currentTask && ["review", "completed", "failed", "cancelled", "archived"].includes(currentTask.status);
73829
- if (!alreadyTerminal && currentTask && currentTask.status === "in_progress") {
74784
+ const didSubmit = this.tasksSubmittedForReview.has(taskId2);
74785
+ if (didSubmit) {
74786
+ this.tasksSubmittedForReview.delete(taskId2);
74787
+ log50.info("Fresh retry finished after submitForReview \u2014 skipping no-submit retry", { taskId: taskId2 });
74788
+ }
74789
+ if (!alreadyTerminal && !didSubmit && currentTask && currentTask.status === "in_progress") {
73830
74790
  const delayMs = withJitter(_TaskService.RETRY_DELAYS_MS[0] ?? 1e4);
73831
74791
  log50.warn(`Fresh retry finished without task_submit_review \u2014 auto-retrying in ${Math.round(delayMs / 1e3)}s`, { taskId: taskId2 });
73832
74792
  this.addTaskNote(taskId2, `[System] Fresh retry finished without task_submit_review. Auto-retrying.`, "system");
@@ -77892,6 +78852,7 @@ ${cleanText}`,
77892
78852
  title: body["title"],
77893
78853
  summary: body["summary"],
77894
78854
  reference: body["reference"],
78855
+ format: body["format"],
77895
78856
  tags: body["tags"],
77896
78857
  taskId: body["taskId"],
77897
78858
  agentId: body["agentId"],
@@ -77919,6 +78880,7 @@ ${cleanText}`,
77919
78880
  title: body["title"],
77920
78881
  summary: body["summary"],
77921
78882
  reference: body["reference"],
78883
+ format: body["format"],
77922
78884
  tags: body["tags"],
77923
78885
  status: body["status"],
77924
78886
  type: body["type"]
@@ -79082,6 +80044,17 @@ EXPLANATION_END`;
79082
80044
  name: t.name,
79083
80045
  description: t.description
79084
80046
  }));
80047
+ const virtualToolNames = new Set(toolList.map((t) => t.name));
80048
+ const VIRTUAL_TOOLS = [
80049
+ { name: "discover_tools", description: "Discover and activate additional tools and skills available to this agent" },
80050
+ { name: "notify_user", description: "Send a notification to a human team member (appears in chat + notification bell)" },
80051
+ { name: "request_user_approval", description: "Request a decision or approval from a human (blocks until response)" },
80052
+ { name: "recall_activity", description: "Query your own execution history and past activity logs" }
80053
+ ];
80054
+ for (const vt of VIRTUAL_TOOLS) {
80055
+ if (!virtualToolNames.has(vt.name))
80056
+ toolList.push(vt);
80057
+ }
79085
80058
  const hb = agent.heartbeat;
79086
80059
  let heartbeatSummary = {};
79087
80060
  try {
@@ -79485,6 +80458,7 @@ EXPLANATION_END`;
79485
80458
  title: body["title"],
79486
80459
  summary: body["summary"] ?? body["content"],
79487
80460
  reference: body["reference"],
80461
+ format: body["format"],
79488
80462
  tags: body["tags"],
79489
80463
  agentId: token.markusAgentId,
79490
80464
  projectId: body["projectId"]
@@ -81193,12 +82167,6 @@ EXPLANATION_END`;
81193
82167
  } catch {
81194
82168
  }
81195
82169
  }
81196
- const billingSummary = this.billingService?.getUsageSummary(orgId2);
81197
- if (billingSummary) {
81198
- llmTokens = Math.max(llmTokens, billingSummary.llmTokens);
81199
- toolCalls = Math.max(toolCalls, billingSummary.toolCalls);
81200
- messages = Math.max(messages, billingSummary.messages);
81201
- }
81202
82170
  this.json(res, 200, {
81203
82171
  usage: {
81204
82172
  orgId: orgId2,
@@ -81206,7 +82174,7 @@ EXPLANATION_END`;
81206
82174
  llmTokens,
81207
82175
  toolCalls,
81208
82176
  messages,
81209
- storageBytes: billingSummary?.storageBytes ?? 0
82177
+ storageBytes: this.billingService?.getUsageSummary(orgId2)?.storageBytes ?? 0
81210
82178
  },
81211
82179
  plan
81212
82180
  });
@@ -81231,7 +82199,8 @@ EXPLANATION_END`;
81231
82199
  requestCount: stats.requestCount,
81232
82200
  toolCalls: stats.toolCalls,
81233
82201
  messages: stats.requestsToday,
81234
- estimatedCost: stats.estimatedCost
82202
+ estimatedCost: stats.estimatedCost,
82203
+ costToday: stats.costToday
81235
82204
  };
81236
82205
  } catch {
81237
82206
  return {
@@ -81246,7 +82215,8 @@ EXPLANATION_END`;
81246
82215
  requestCount: 0,
81247
82216
  toolCalls: 0,
81248
82217
  messages: 0,
81249
- estimatedCost: 0
82218
+ estimatedCost: 0,
82219
+ costToday: 0
81250
82220
  };
81251
82221
  }
81252
82222
  });
@@ -81478,23 +82448,31 @@ EXPLANATION_END`;
81478
82448
  return;
81479
82449
  }
81480
82450
  const body = await this.readBody(req);
81481
- const { defaultProvider } = body;
81482
- if (!defaultProvider) {
81483
- this.json(res, 400, { error: "defaultProvider is required" });
82451
+ const { defaultProvider, autoFallback } = body;
82452
+ if (!defaultProvider && autoFallback === void 0) {
82453
+ this.json(res, 400, { error: "defaultProvider or autoFallback is required" });
81484
82454
  return;
81485
82455
  }
81486
82456
  try {
81487
- this.llmRouter.setDefaultProvider(defaultProvider);
82457
+ const configUpdates = {};
82458
+ if (defaultProvider) {
82459
+ this.llmRouter.setDefaultProvider(defaultProvider);
82460
+ configUpdates.defaultProvider = defaultProvider;
82461
+ }
82462
+ if (typeof autoFallback === "boolean") {
82463
+ this.llmRouter.setAutoFallback(autoFallback);
82464
+ configUpdates.autoFallback = autoFallback;
82465
+ }
81488
82466
  try {
81489
- saveConfig({ llm: { defaultProvider } }, this.markusConfigPath);
82467
+ saveConfig({ llm: configUpdates }, this.markusConfigPath);
81490
82468
  } catch (e) {
81491
- log56.warn("Failed to persist defaultProvider to config file", { error: String(e) });
82469
+ log56.warn("Failed to persist LLM settings to config file", { error: String(e) });
81492
82470
  }
81493
82471
  this.auditService?.record({
81494
82472
  orgId: "system",
81495
82473
  type: "settings_changed",
81496
- action: "llm_default_provider",
81497
- detail: `defaultProvider=${defaultProvider}`,
82474
+ action: "llm_settings",
82475
+ detail: `${defaultProvider ? `defaultProvider=${defaultProvider}` : ""}${autoFallback !== void 0 ? ` autoFallback=${autoFallback}` : ""}`.trim(),
81498
82476
  userId: auth.userId,
81499
82477
  success: true
81500
82478
  });
@@ -81618,6 +82596,70 @@ EXPLANATION_END`;
81618
82596
  });
81619
82597
  return;
81620
82598
  }
82599
+ if (path === "/api/settings/search" && req.method === "GET") {
82600
+ const { loadConfig: loadCfg } = await Promise.resolve().then(() => (init_dist(), dist_exports));
82601
+ const currentConfig = loadCfg(this.markusConfigPath);
82602
+ const search = currentConfig.integrations?.search ?? {};
82603
+ const mask = (key2) => key2 ? "***" + key2.slice(-4) : "";
82604
+ this.json(res, 200, {
82605
+ serper: { configured: !!search.serperApiKey || !!process.env["SERPER_API_KEY"], preview: mask(search.serperApiKey || process.env["SERPER_API_KEY"]) },
82606
+ brave: { configured: !!search.braveApiKey || !!process.env["BRAVE_SEARCH_API_KEY"], preview: mask(search.braveApiKey || process.env["BRAVE_SEARCH_API_KEY"]) },
82607
+ bocha: { configured: !!search.bochaApiKey || !!process.env["BOCHA_API_KEY"], preview: mask(search.bochaApiKey || process.env["BOCHA_API_KEY"]) }
82608
+ });
82609
+ return;
82610
+ }
82611
+ if (path === "/api/settings/search" && req.method === "POST") {
82612
+ const auth = await this.requireAuth(req, res);
82613
+ if (!auth)
82614
+ return;
82615
+ const body = await this.readBody(req);
82616
+ const updates = {};
82617
+ if (typeof body["serperApiKey"] === "string") {
82618
+ updates.serperApiKey = body["serperApiKey"] || void 0;
82619
+ if (body["serperApiKey"])
82620
+ process.env["SERPER_API_KEY"] = body["serperApiKey"];
82621
+ else
82622
+ delete process.env["SERPER_API_KEY"];
82623
+ }
82624
+ if (typeof body["braveApiKey"] === "string") {
82625
+ updates.braveApiKey = body["braveApiKey"] || void 0;
82626
+ if (body["braveApiKey"])
82627
+ process.env["BRAVE_SEARCH_API_KEY"] = body["braveApiKey"];
82628
+ else
82629
+ delete process.env["BRAVE_SEARCH_API_KEY"];
82630
+ }
82631
+ if (typeof body["bochaApiKey"] === "string") {
82632
+ updates.bochaApiKey = body["bochaApiKey"] || void 0;
82633
+ if (body["bochaApiKey"])
82634
+ process.env["BOCHA_API_KEY"] = body["bochaApiKey"];
82635
+ else
82636
+ delete process.env["BOCHA_API_KEY"];
82637
+ }
82638
+ try {
82639
+ saveConfig({ integrations: { search: updates } }, this.markusConfigPath);
82640
+ } catch (e) {
82641
+ log56.warn("Failed to persist search settings", { error: String(e) });
82642
+ }
82643
+ this.auditService?.record({
82644
+ orgId: "system",
82645
+ type: "settings_changed",
82646
+ action: "search",
82647
+ detail: "Search API key settings updated",
82648
+ userId: auth.userId,
82649
+ success: true,
82650
+ metadata: { keys: Object.keys(updates) }
82651
+ });
82652
+ const { loadConfig: loadCfg } = await Promise.resolve().then(() => (init_dist(), dist_exports));
82653
+ const currentConfig = loadCfg(this.markusConfigPath);
82654
+ const search = currentConfig.integrations?.search ?? {};
82655
+ const mask = (key2) => key2 ? "***" + key2.slice(-4) : "";
82656
+ this.json(res, 200, {
82657
+ serper: { configured: !!search.serperApiKey || !!process.env["SERPER_API_KEY"], preview: mask(search.serperApiKey || process.env["SERPER_API_KEY"]) },
82658
+ brave: { configured: !!search.braveApiKey || !!process.env["BRAVE_SEARCH_API_KEY"], preview: mask(search.braveApiKey || process.env["BRAVE_SEARCH_API_KEY"]) },
82659
+ bocha: { configured: !!search.bochaApiKey || !!process.env["BOCHA_API_KEY"], preview: mask(search.bochaApiKey || process.env["BOCHA_API_KEY"]) }
82660
+ });
82661
+ return;
82662
+ }
81621
82663
  if (path === "/api/settings/llm/models" && req.method === "GET") {
81622
82664
  if (!this.llmRouter) {
81623
82665
  this.json(res, 200, { models: [] });
@@ -82884,6 +83926,8 @@ EXPLANATION_END`;
82884
83926
  const home = homedir22();
82885
83927
  const results = {};
82886
83928
  const mdExts = [".md", ".markdown"];
83929
+ const htmlExts = [".html", ".htm"];
83930
+ const jsonExts = [".json"];
82887
83931
  const imageExts = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
82888
83932
  for (const p of paths.slice(0, 50)) {
82889
83933
  try {
@@ -82899,6 +83943,10 @@ EXPLANATION_END`;
82899
83943
  let type = "text";
82900
83944
  if (mdExts.includes(ext))
82901
83945
  type = "markdown";
83946
+ else if (htmlExts.includes(ext))
83947
+ type = "html";
83948
+ else if (jsonExts.includes(ext))
83949
+ type = "json";
82902
83950
  else if (imageExts.includes(ext))
82903
83951
  type = "image";
82904
83952
  else if (!isFile)
@@ -82932,6 +83980,27 @@ EXPLANATION_END`;
82932
83980
  return;
82933
83981
  }
82934
83982
  const stat = statSync6(resolved);
83983
+ if (stat.isDirectory()) {
83984
+ const { readdirSync: readdirSync13 } = await import("node:fs");
83985
+ const { join: join30, extname: extDir } = await import("node:path");
83986
+ const entries2 = readdirSync13(resolved, { withFileTypes: true }).filter((e) => !e.name.startsWith(".")).map((e) => {
83987
+ const full = join30(resolved, e.name);
83988
+ const isDir = e.isDirectory();
83989
+ let size;
83990
+ try {
83991
+ if (!isDir)
83992
+ size = statSync6(full).size;
83993
+ } catch {
83994
+ }
83995
+ return { name: e.name, path: full, isDirectory: isDir, size, ext: isDir ? "" : extDir(e.name).toLowerCase() };
83996
+ }).sort((a, b) => {
83997
+ if (a.isDirectory !== b.isDirectory)
83998
+ return a.isDirectory ? -1 : 1;
83999
+ return a.name.localeCompare(b.name);
84000
+ });
84001
+ this.json(res, 200, { type: "directory", name: resolved.split("/").pop(), path: resolved, entries: entries2 });
84002
+ return;
84003
+ }
82935
84004
  if (!stat.isFile()) {
82936
84005
  this.json(res, 400, { error: "Path is not a file" });
82937
84006
  return;
@@ -82955,8 +84024,20 @@ EXPLANATION_END`;
82955
84024
  } else {
82956
84025
  const content = readFileSync26(resolved, "utf-8");
82957
84026
  const mdExts = [".md", ".markdown"];
84027
+ const htmlExts = [".html", ".htm"];
84028
+ const jsonExts = [".json"];
84029
+ const csvExts = [".csv", ".tsv"];
84030
+ let fileType = "text";
84031
+ if (mdExts.includes(ext))
84032
+ fileType = "markdown";
84033
+ else if (htmlExts.includes(ext))
84034
+ fileType = "html";
84035
+ else if (jsonExts.includes(ext))
84036
+ fileType = "json";
84037
+ else if (csvExts.includes(ext))
84038
+ fileType = "csv";
82958
84039
  this.json(res, 200, {
82959
- type: mdExts.includes(ext) ? "markdown" : "text",
84040
+ type: fileType,
82960
84041
  name: resolved.split("/").pop(),
82961
84042
  content
82962
84043
  });
@@ -83896,6 +84977,7 @@ EXPLANATION_END`;
83896
84977
  exact("/api/settings/llm/models", "GET"),
83897
84978
  exact("/api/settings/agent", "GET", "POST"),
83898
84979
  exact("/api/settings/browser", "GET", "POST"),
84980
+ exact("/api/settings/search", "GET", "POST"),
83899
84981
  exact("/api/settings/env-models", "GET", "POST"),
83900
84982
  exact("/api/settings/detect-ollama", "GET"),
83901
84983
  exact("/api/settings/export", "POST"),
@@ -84096,7 +85178,8 @@ EXPLANATION_END`;
84096
85178
  const criticalAgents = agentRanking.filter((a) => a.healthScore < 50);
84097
85179
  const totalTokenCost = agentRanking.reduce((s2, a) => s2 + a.tokenUsage.cost, 0);
84098
85180
  const totalInteractions = agentRanking.reduce((s2, a) => s2 + a.totalInteractions, 0);
84099
- const taskSuccessRate = taskDashboard.totalTasks > 0 ? Math.round(taskDashboard.statusCounts.completed / taskDashboard.totalTasks * 100) : 0;
85181
+ const terminalTaskCount = taskDashboard.statusCounts.completed + taskDashboard.statusCounts.failed + taskDashboard.statusCounts.rejected;
85182
+ const taskSuccessRate = terminalTaskCount > 0 ? Math.round(taskDashboard.statusCounts.completed / terminalTaskCount * 100) : 0;
84100
85183
  const blockedTasks = taskDashboard.statusCounts.blocked ?? 0;
84101
85184
  return {
84102
85185
  period,
@@ -84118,6 +85201,7 @@ EXPLANATION_END`;
84118
85201
  statusCounts: taskDashboard.statusCounts,
84119
85202
  successRate: taskSuccessRate,
84120
85203
  blockedCount: blockedTasks,
85204
+ stuckBlockedCount: taskDashboard.stuckBlockedCount,
84121
85205
  averageCompletionTimeMs: taskDashboard.averageCompletionTimeMs,
84122
85206
  recentActivity: taskDashboard.recentActivity.slice(0, 10)
84123
85207
  },
@@ -84730,7 +85814,7 @@ var init_hitl_service = __esm({
84730
85814
  });
84731
85815
 
84732
85816
  // ../org-manager/dist/billing-service.js
84733
- import { randomBytes as randomBytes4 } from "node:crypto";
85817
+ import { randomBytes as randomBytes5 } from "node:crypto";
84734
85818
  var log58, DEFAULT_PLANS, keyCounter, BillingService;
84735
85819
  var init_billing_service = __esm({
84736
85820
  "../org-manager/dist/billing-service.js"() {
@@ -84864,7 +85948,7 @@ var init_billing_service = __esm({
84864
85948
  }
84865
85949
  createAPIKey(orgId2, name, scopes = ["*"], expiresInDays) {
84866
85950
  const id = `mk_${(++keyCounter).toString(36)}_${Date.now().toString(36)}`;
84867
- const key2 = `mk_${randomBytes4(32).toString("hex")}`;
85951
+ const key2 = `mk_${randomBytes5(32).toString("hex")}`;
84868
85952
  const now3 = /* @__PURE__ */ new Date();
84869
85953
  const apiKey = {
84870
85954
  id,
@@ -86216,6 +87300,8 @@ var init_deliverable_service = __esm({
86216
87300
  summary: opts.summary,
86217
87301
  tags: opts.tags ?? existing.tags
86218
87302
  };
87303
+ if (opts.format !== void 0)
87304
+ patch.format = opts.format;
86219
87305
  if (opts.artifactType !== void 0)
86220
87306
  patch.artifactType = opts.artifactType;
86221
87307
  if (opts.artifactData !== void 0)
@@ -86244,6 +87330,7 @@ var init_deliverable_service = __esm({
86244
87330
  title: opts.title,
86245
87331
  summary: opts.summary,
86246
87332
  reference: ref ?? "",
87333
+ format: opts.format,
86247
87334
  tags: opts.tags ?? [],
86248
87335
  status: "active",
86249
87336
  taskId: opts.taskId,
@@ -86264,6 +87351,7 @@ var init_deliverable_service = __esm({
86264
87351
  title: opts.title,
86265
87352
  summary: opts.summary,
86266
87353
  reference: ref ?? "",
87354
+ format: opts.format,
86267
87355
  tags: opts.tags ?? [],
86268
87356
  status: "active",
86269
87357
  taskId: opts.taskId,
@@ -86351,6 +87439,8 @@ var init_deliverable_service = __esm({
86351
87439
  d.summary = data.summary;
86352
87440
  if (data.reference !== void 0)
86353
87441
  d.reference = data.reference;
87442
+ if (data.format !== void 0)
87443
+ d.format = data.format;
86354
87444
  if (data.tags !== void 0)
86355
87445
  d.tags = data.tags;
86356
87446
  if (data.status !== void 0)
@@ -86530,6 +87620,7 @@ var init_deliverable_service = __esm({
86530
87620
  title: r.title,
86531
87621
  summary: r.summary,
86532
87622
  reference: r.reference,
87623
+ format: r.format ?? void 0,
86533
87624
  tags: this.parseTags(r.tags),
86534
87625
  status: r.status,
86535
87626
  taskId: r.taskId ?? void 0,
@@ -86786,12 +87877,16 @@ var init_report_service = __esm({
86786
87877
  }
86787
87878
  buildCostSummary(scopeId, start, end) {
86788
87879
  const usage = this.billingService.getUsageSummaryForPeriod(scopeId, start, end);
87880
+ const input = Math.round(usage.llmTokens * 0.7);
87881
+ const output = usage.llmTokens - input;
87882
+ const estimatedCost = input / 1e6 * 3 + output / 1e6 * 15;
87883
+ const cost = Math.round(estimatedCost * 1e4) / 1e4;
86789
87884
  return {
86790
87885
  totalTokens: usage.llmTokens,
86791
- totalEstimatedCost: usage.llmTokens * 3e-6,
87886
+ totalEstimatedCost: cost,
86792
87887
  byAgent: [],
86793
87888
  byCategory: [
86794
- { category: "llm_tokens", tokens: usage.llmTokens, cost: usage.llmTokens * 3e-6 },
87889
+ { category: "llm_tokens", tokens: usage.llmTokens, cost },
86795
87890
  { category: "tool_calls", tokens: 0, cost: 0 }
86796
87891
  ],
86797
87892
  trend: "stable"
@@ -87115,17 +88210,21 @@ var init_stale_detector = __esm({
87115
88210
  });
87116
88211
 
87117
88212
  // ../org-manager/dist/scheduled-task-runner.js
87118
- var log69, ScheduledTaskRunner;
88213
+ var log69, MIN_STAGGER_MS, MAX_STAGGER_MS, ScheduledTaskRunner;
87119
88214
  var init_scheduled_task_runner = __esm({
87120
88215
  "../org-manager/dist/scheduled-task-runner.js"() {
87121
88216
  "use strict";
87122
88217
  init_dist();
87123
88218
  log69 = createLogger("scheduled-task-runner");
88219
+ MIN_STAGGER_MS = 2 * 6e4;
88220
+ MAX_STAGGER_MS = 15 * 6e4;
87124
88221
  ScheduledTaskRunner = class {
87125
88222
  taskService;
87126
88223
  pollIntervalMs;
87127
88224
  timer;
88225
+ staggerTimers = [];
87128
88226
  running = false;
88227
+ startedAt = 0;
87129
88228
  constructor(taskService, pollIntervalMs = 6e4) {
87130
88229
  this.taskService = taskService;
87131
88230
  this.pollIntervalMs = pollIntervalMs;
@@ -87134,6 +88233,8 @@ var init_scheduled_task_runner = __esm({
87134
88233
  if (this.running)
87135
88234
  return;
87136
88235
  this.running = true;
88236
+ this.startedAt = Date.now();
88237
+ this.tick().catch((e) => log69.error("Initial scheduled task tick failed", { error: String(e) }));
87137
88238
  this.timer = setInterval(() => {
87138
88239
  this.tick().catch((e) => log69.error("Scheduled task tick failed", { error: String(e) }));
87139
88240
  }, this.pollIntervalMs);
@@ -87144,15 +88245,26 @@ var init_scheduled_task_runner = __esm({
87144
88245
  clearInterval(this.timer);
87145
88246
  this.timer = void 0;
87146
88247
  }
88248
+ for (const t of this.staggerTimers)
88249
+ clearTimeout(t);
88250
+ this.staggerTimers = [];
87147
88251
  this.running = false;
87148
88252
  log69.info("ScheduledTaskRunner stopped");
87149
88253
  }
87150
88254
  isRunning() {
87151
88255
  return this.running;
87152
88256
  }
88257
+ /**
88258
+ * Returns true during the startup grace period (first poll cycle after
88259
+ * start). Used to stagger overdue tasks instead of firing them all at once.
88260
+ */
88261
+ isStartupPhase() {
88262
+ return Date.now() - this.startedAt < this.pollIntervalMs;
88263
+ }
87153
88264
  async tick() {
87154
88265
  const scheduledTasks = this.taskService.listScheduledTasks();
87155
88266
  const now3 = Date.now();
88267
+ const startup = this.isStartupPhase();
87156
88268
  for (const task of scheduledTasks) {
87157
88269
  if (!task.scheduleConfig)
87158
88270
  continue;
@@ -87169,6 +88281,22 @@ var init_scheduled_task_runner = __esm({
87169
88281
  if (config.paused) {
87170
88282
  continue;
87171
88283
  }
88284
+ if (startup && nextRun < this.startedAt) {
88285
+ const delay = MIN_STAGGER_MS + Math.random() * (MAX_STAGGER_MS - MIN_STAGGER_MS);
88286
+ log69.info("Staggering overdue scheduled task", {
88287
+ taskId: task.id,
88288
+ title: task.title,
88289
+ overdueBy: `${Math.round((now3 - nextRun) / 6e4)}m`,
88290
+ firingIn: `${Math.round(delay / 6e4)}m`
88291
+ });
88292
+ const timer = setTimeout(() => {
88293
+ if (!this.running)
88294
+ return;
88295
+ this.fireScheduledTask(task).catch((e) => log69.error("Failed to fire staggered scheduled task", { taskId: task.id, error: String(e) }));
88296
+ }, delay);
88297
+ this.staggerTimers.push(timer);
88298
+ continue;
88299
+ }
87172
88300
  try {
87173
88301
  await this.fireScheduledTask(task);
87174
88302
  } catch (e) {
@@ -87268,7 +88396,8 @@ function openSqlite(dbPath) {
87268
88396
  { table: "projects", column: "created_by", sql: "ALTER TABLE projects ADD COLUMN created_by TEXT" },
87269
88397
  { table: "approvals", column: "target_user_id", sql: "ALTER TABLE approvals ADD COLUMN target_user_id TEXT" },
87270
88398
  { table: "users", column: "deleted_at", sql: "ALTER TABLE users ADD COLUMN deleted_at TEXT" },
87271
- { table: "agents", column: "deleted_at", sql: "ALTER TABLE agents ADD COLUMN deleted_at TEXT" }
88399
+ { table: "agents", column: "deleted_at", sql: "ALTER TABLE agents ADD COLUMN deleted_at TEXT" },
88400
+ { table: "deliverables", column: "format", sql: "ALTER TABLE deliverables ADD COLUMN format TEXT" }
87272
88401
  ];
87273
88402
  for (const m of migrations) {
87274
88403
  const cols = _db.prepare(`PRAGMA table_info(${m.table})`).all();
@@ -87573,6 +88702,7 @@ CREATE TABLE IF NOT EXISTS deliverables (
87573
88702
  title TEXT NOT NULL,
87574
88703
  summary TEXT NOT NULL DEFAULT '',
87575
88704
  reference TEXT NOT NULL DEFAULT '',
88705
+ format TEXT,
87576
88706
  tags TEXT NOT NULL DEFAULT '[]',
87577
88707
  status TEXT NOT NULL DEFAULT 'active',
87578
88708
  task_id TEXT,
@@ -89550,11 +90680,11 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89550
90680
  async create(data) {
89551
90681
  const n2 = now2();
89552
90682
  this.db.prepare(`
89553
- INSERT INTO deliverables (id, type, title, summary, reference, tags, status,
90683
+ INSERT INTO deliverables (id, type, title, summary, reference, format, tags, status,
89554
90684
  task_id, agent_id, project_id, requirement_id, diff_stats, test_results,
89555
90685
  artifact_type, artifact_data, access_count, created_at, updated_at)
89556
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
89557
- `).run(data.id, data.type, data.title, data.summary, data.reference ?? "", toJson(data.tags ?? []), data.status ?? "active", data.taskId ?? null, data.agentId ?? null, data.projectId ?? null, data.requirementId ?? null, data.diffStats ? toJson(data.diffStats) : null, data.testResults ? toJson(data.testResults) : null, data.artifactType ?? null, data.artifactData ? toJson(data.artifactData) : null, n2, n2);
90686
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
90687
+ `).run(data.id, data.type, data.title, data.summary, data.reference ?? "", data.format ?? null, toJson(data.tags ?? []), data.status ?? "active", data.taskId ?? null, data.agentId ?? null, data.projectId ?? null, data.requirementId ?? null, data.diffStats ? toJson(data.diffStats) : null, data.testResults ? toJson(data.testResults) : null, data.artifactType ?? null, data.artifactData ? toJson(data.artifactData) : null, n2, n2);
89558
90688
  return this.findById(data.id);
89559
90689
  }
89560
90690
  async findById(id) {
@@ -89617,6 +90747,10 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89617
90747
  sets.push("reference = ?");
89618
90748
  vals.push(patch.reference);
89619
90749
  }
90750
+ if (patch.format !== void 0) {
90751
+ sets.push("format = ?");
90752
+ vals.push(patch.format);
90753
+ }
89620
90754
  if (patch.type !== void 0) {
89621
90755
  sets.push("type = ?");
89622
90756
  vals.push(patch.type);
@@ -89665,6 +90799,7 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89665
90799
  title: r["title"],
89666
90800
  summary: r["summary"],
89667
90801
  reference: r["reference"],
90802
+ format: r["format"] ?? null,
89668
90803
  tags: fromJson(r["tags"]) ?? [],
89669
90804
  status: r["status"],
89670
90805
  taskId: r["task_id"],
@@ -91204,6 +92339,7 @@ var init_logger2 = __esm({
91204
92339
 
91205
92340
  // src/utils/browser.ts
91206
92341
  import { exec } from "node:child_process";
92342
+ import { get as httpGet } from "node:http";
91207
92343
  import { platform as platform2 } from "node:os";
91208
92344
  function openBrowser(url) {
91209
92345
  if (process.env["NO_BROWSER"]) return;
@@ -91214,6 +92350,27 @@ function openBrowser(url) {
91214
92350
  }
91215
92351
  });
91216
92352
  }
92353
+ function openBrowserAfterHealthCheck(uiUrl, healthUrl, intervalMs = 500, maxMs = 3e4) {
92354
+ if (process.env["NO_BROWSER"]) return;
92355
+ const deadline = Date.now() + maxMs;
92356
+ const check2 = () => {
92357
+ const req = httpGet(healthUrl, (res) => {
92358
+ res.resume();
92359
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
92360
+ openBrowser(uiUrl);
92361
+ } else if (Date.now() < deadline) {
92362
+ setTimeout(check2, intervalMs);
92363
+ }
92364
+ });
92365
+ req.on("error", () => {
92366
+ if (Date.now() < deadline) setTimeout(check2, intervalMs);
92367
+ });
92368
+ req.setTimeout(2e3, () => {
92369
+ req.destroy();
92370
+ });
92371
+ };
92372
+ check2();
92373
+ }
91217
92374
  var init_browser = __esm({
91218
92375
  "src/utils/browser.ts"() {
91219
92376
  "use strict";
@@ -92156,6 +93313,9 @@ async function createServices(config) {
92156
93313
  llmRouter.setProviderEnabled(name, false);
92157
93314
  }
92158
93315
  }
93316
+ if (config.llm.autoFallback === false) {
93317
+ llmRouter.setAutoFallback(false);
93318
+ }
92159
93319
  if (config.llm.customModels) {
92160
93320
  for (const [providerName, models] of Object.entries(config.llm.customModels)) {
92161
93321
  for (const model of models) {
@@ -92330,6 +93490,9 @@ async function startServer(config, values) {
92330
93490
  if (config.integrations?.search?.braveApiKey && !process.env["BRAVE_SEARCH_API_KEY"]) {
92331
93491
  process.env["BRAVE_SEARCH_API_KEY"] = config.integrations.search.braveApiKey;
92332
93492
  }
93493
+ if (config.integrations?.search?.bochaApiKey && !process.env["BOCHA_API_KEY"]) {
93494
+ process.env["BOCHA_API_KEY"] = config.integrations.search.bochaApiKey;
93495
+ }
92333
93496
  const apiPort = Number(values["port"]) || config.server.apiPort;
92334
93497
  const {
92335
93498
  orgService,
@@ -92579,20 +93742,37 @@ async function startServer(config, values) {
92579
93742
  const sessionUserId = targetUserId || defaultSessionUserId;
92580
93743
  const mainSession = storage.chatSessionRepo.getOrCreateMainSession(agentId2, sessionUserId);
92581
93744
  const agent = agentManager.getAgent(agentId2);
93745
+ const contextParts = [];
93746
+ if (taskId2) contextParts.push(`task_id=${taskId2}`);
93747
+ if (requirementId2) contextParts.push(`requirement_id=${requirementId2}`);
93748
+ if (priority && priority !== "normal") contextParts.push(`priority=${priority}`);
93749
+ const contextSuffix = contextParts.length > 0 ? `
93750
+
93751
+ <!-- notify_context: ${contextParts.join(", ")} -->` : "";
92582
93752
  const formattedMsg = `**${title}**
92583
93753
 
92584
- ${body}`;
93754
+ ${body}${contextSuffix}`;
93755
+ const msgMetadata = {
93756
+ notifyUser: true,
93757
+ priority: priority ?? "normal",
93758
+ ...taskId2 ? { taskId: taskId2 } : {},
93759
+ ...requirementId2 ? { requirementId: requirementId2 } : {}
93760
+ };
92585
93761
  const msg = storage.chatSessionRepo.appendMessage(
92586
93762
  mainSession.id,
92587
93763
  agentId2,
92588
93764
  "assistant",
92589
93765
  formattedMsg,
92590
93766
  0,
92591
- {}
93767
+ msgMetadata
92592
93768
  );
92593
93769
  storage.chatSessionRepo.updateLastMessage(mainSession.id);
92594
93770
  ws.broadcastProactiveMessage(agentId2, agent.config.name, mainSession.id, msg.id, formattedMsg, {
92595
- isMainSession: true
93771
+ isMainSession: true,
93772
+ notifyUser: true,
93773
+ priority: priority ?? "normal",
93774
+ taskId: taskId2,
93775
+ requirementId: requirementId2
92596
93776
  }, sessionUserId);
92597
93777
  const hasTask = !!taskId2;
92598
93778
  hitlService.notify({
@@ -93236,13 +94416,9 @@ ${reason}`;
93236
94416
  const logFile = getStartupLogFile();
93237
94417
  const logFileName = logFile.split("/").pop() ?? logFile;
93238
94418
  const uiUrl = `http://localhost:${apiPort}`;
94419
+ progress.finish(uiUrl);
93239
94420
  if (!process.env["NO_BROWSER"] && webUiDir) {
93240
- progress.finish(uiUrl);
93241
- setTimeout(() => {
93242
- openBrowser(uiUrl);
93243
- }, 500);
93244
- } else {
93245
- progress.finish(uiUrl);
94421
+ openBrowserAfterHealthCheck(uiUrl, `${uiUrl}/api/health`);
93246
94422
  }
93247
94423
  checkForUpdate().then((info4) => {
93248
94424
  if (info4.updateAvailable) {
@@ -96351,7 +97527,7 @@ var connect_exports = {};
96351
97527
  __export(connect_exports, {
96352
97528
  registerConnectCommands: () => registerConnectCommands
96353
97529
  });
96354
- import { randomBytes as randomBytes5 } from "node:crypto";
97530
+ import { randomBytes as randomBytes6 } from "node:crypto";
96355
97531
  function registerConnectCommands(program2) {
96356
97532
  const root = program2.command("connect").description("Connect / disconnect external agent platforms");
96357
97533
  root.command("scan").description("Scan for installed agent platforms that can be connected").action(async (_opts, cmd) => {
@@ -96410,7 +97586,7 @@ function registerConnectCommands(program2) {
96410
97586
  fail(`Unknown platform "${platform3}". Run \`markus connect list\` to see available connectors.`);
96411
97587
  return;
96412
97588
  }
96413
- const agentId2 = opts.agentId || `${platform3}-${randomBytes5(4).toString("hex")}`;
97589
+ const agentId2 = opts.agentId || `${platform3}-${randomBytes6(4).toString("hex")}`;
96414
97590
  const agentName = opts.agentName || connector.defaultAgentName || `${connector.displayName} Agent`;
96415
97591
  const capabilities = opts.capabilities ? String(opts.capabilities).split(",").map((s2) => s2.trim()).filter(Boolean) : connector.defaultCapabilities ?? [];
96416
97592
  const client = createClient(g);
@@ -96584,7 +97760,7 @@ __export(install_agent_exports, {
96584
97760
  registerInstallAgentCommands: () => registerInstallAgentCommands
96585
97761
  });
96586
97762
  import { execSync as execSync5 } from "node:child_process";
96587
- import { randomBytes as randomBytes6 } from "node:crypto";
97763
+ import { randomBytes as randomBytes7 } from "node:crypto";
96588
97764
  function registerInstallAgentCommands(program2) {
96589
97765
  program2.command("install <platform>").description("Install an external agent platform and connect it to Markus").option("--org-id <id>", "Organization ID", "default").option("--agent-name <name>", "Agent display name").option("--skip-install", "Skip npm install (platform already installed)").option("--skip-init", "Skip platform initialization").option("--skip-connect", "Only install, do not register with Markus").action(async (platform3, opts, cmd) => {
96590
97766
  const g = cmd.optsWithGlobals();
@@ -96640,7 +97816,7 @@ function registerInstallAgentCommands(program2) {
96640
97816
  }
96641
97817
  const client = createClient(g);
96642
97818
  const serverUrl = g.server || process.env["MARKUS_API_URL"] || "http://localhost:8056";
96643
- const agentId2 = `${platform3}-${randomBytes6(4).toString("hex")}`;
97819
+ const agentId2 = `${platform3}-${randomBytes7(4).toString("hex")}`;
96644
97820
  const agentName = opts.agentName || connector.defaultAgentName || `${connector.displayName} Agent`;
96645
97821
  const capabilities = connector.defaultCapabilities ?? [];
96646
97822
  try {