@markus-global/cli 0.6.7 → 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) {
@@ -6403,6 +6499,13 @@ var init_context_engine = __esm({
6403
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.");
6404
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.');
6405
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.");
6508
+ stable.push("");
6406
6509
  stable.push("\n## Task & Requirement Workflow");
6407
6510
  stable.push("");
6408
6511
  stable.push("**Requirements** (governance gate):");
@@ -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 {
@@ -8432,7 +8572,8 @@ var init_tool_selector = __esm({
8432
8572
  title: { type: "string", description: "Short headline (1 line)" },
8433
8573
  body: { type: "string", description: "Full message content. Be thorough \u2014 this is what the recipient reads in chat." },
8434
8574
  priority: { type: "string", enum: ["low", "normal", "high", "urgent"], description: "Default: normal" },
8435
- 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)." }
8436
8577
  },
8437
8578
  required: ["title", "body"]
8438
8579
  }
@@ -8480,6 +8621,22 @@ var init_tool_selector = __esm({
8480
8621
  required: ["operation"]
8481
8622
  }
8482
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
+ });
8483
8640
  log10.debug("Tool selection complete", {
8484
8641
  total: opts.allTools.size,
8485
8642
  selected: result.length,
@@ -8644,8 +8801,310 @@ var init_security = __esm({
8644
8801
  }
8645
8802
  });
8646
8803
 
8647
- // ../core/dist/tools/shell.js
8804
+ // ../core/dist/tools/shell-session.js
8648
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";
8649
9108
  import { resolve as resolve4, normalize, sep } from "node:path";
8650
9109
  function injectGitCommitMeta(command, meta) {
8651
9110
  if (!meta)
@@ -8718,6 +9177,10 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8718
9177
  timeout_ms: {
8719
9178
  type: "number",
8720
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."
8721
9184
  }
8722
9185
  },
8723
9186
  required: ["command"]
@@ -8774,22 +9237,71 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8774
9237
  }
8775
9238
  }
8776
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
+ }
8777
9266
  return new Promise((resolve20) => {
8778
- 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], {
8779
9275
  cwd: effectiveCwd ?? void 0,
8780
9276
  stdio: ["ignore", "pipe", "pipe"],
9277
+ detached: true,
8781
9278
  env: { ...process.env }
8782
9279
  });
9280
+ child.unref();
8783
9281
  let stdout = "";
8784
9282
  let stderr = "";
8785
9283
  let killed = false;
8786
9284
  const maxBuffer = 10 * 1024 * 1024;
8787
9285
  const timeout = setTimeout(() => {
8788
9286
  killed = true;
8789
- child.kill("SIGTERM");
9287
+ try {
9288
+ process.kill(-child.pid, "SIGTERM");
9289
+ } catch {
9290
+ }
8790
9291
  setTimeout(() => {
8791
- if (!child.killed)
8792
- 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
+ }));
8793
9305
  }, 2e3);
8794
9306
  }, timeoutMs);
8795
9307
  let outputBuffer = "";
@@ -8828,27 +9340,29 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8828
9340
  clearTimeout(flushTimer);
8829
9341
  flushOutput();
8830
9342
  }
9343
+ const cleanOut = sanitizeForLLM(stdout);
9344
+ const cleanErr = sanitizeForLLM(stderr);
8831
9345
  if (killed) {
8832
- resolve20(JSON.stringify({
9346
+ settle(JSON.stringify({
8833
9347
  status: "error",
8834
9348
  error: `Command timed out after ${timeoutMs}ms`,
8835
9349
  exitCode: code,
8836
- stdout: stdout.slice(0, 4e3),
8837
- stderr: stderr.slice(0, 4e3)
9350
+ stdout: cleanOut.slice(0, 4e3),
9351
+ stderr: cleanErr.slice(0, 4e3)
8838
9352
  }));
8839
9353
  } else if (code !== 0) {
8840
- resolve20(JSON.stringify({
9354
+ settle(JSON.stringify({
8841
9355
  status: "error",
8842
- error: stderr.trim() || `Process exited with code ${code}`,
9356
+ error: cleanErr.trim() || `Process exited with code ${code}`,
8843
9357
  exitCode: code,
8844
- stdout: stdout.slice(0, 4e3),
8845
- stderr: stderr.slice(0, 4e3)
9358
+ stdout: cleanOut.slice(0, 4e3),
9359
+ stderr: cleanErr.slice(0, 4e3)
8846
9360
  }));
8847
9361
  } else {
8848
- resolve20(JSON.stringify({
9362
+ settle(JSON.stringify({
8849
9363
  status: "success",
8850
- stdout: stdout.trim() || void 0,
8851
- stderr: stderr.trim() || void 0
9364
+ stdout: cleanOut.trim() || void 0,
9365
+ stderr: cleanErr.trim() || void 0
8852
9366
  }));
8853
9367
  }
8854
9368
  });
@@ -8858,11 +9372,11 @@ function createShellTool(security, workspacePath, agentMeta, policy, onCommandAp
8858
9372
  clearTimeout(flushTimer);
8859
9373
  flushOutput();
8860
9374
  }
8861
- resolve20(JSON.stringify({
9375
+ settle(JSON.stringify({
8862
9376
  status: "error",
8863
9377
  error: err.message,
8864
- stdout: stdout.slice(0, 4e3),
8865
- stderr: stderr.slice(0, 4e3)
9378
+ stdout: sanitizeForLLM(stdout).slice(0, 4e3),
9379
+ stderr: sanitizeForLLM(stderr).slice(0, 4e3)
8866
9380
  }));
8867
9381
  });
8868
9382
  });
@@ -8875,6 +9389,7 @@ var init_shell = __esm({
8875
9389
  "use strict";
8876
9390
  init_dist();
8877
9391
  init_security();
9392
+ init_shell_session();
8878
9393
  GIT_ALWAYS_DENY = [];
8879
9394
  GIT_NEEDS_APPROVAL = [
8880
9395
  { pattern: /\bgit\s+push\s+--force/, label: "force push (--force)" },
@@ -41343,44 +41858,32 @@ async function searchBrave(query2, maxResults) {
41343
41858
  date: r.page_age
41344
41859
  }));
41345
41860
  }
41346
- async function searchBing(query2, maxResults) {
41347
- 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");
41348
41865
  let res;
41349
41866
  try {
41350
- res = await proxyFetch(`https://www.bing.com/search?q=${encoded}`, {
41351
- 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 })
41352
41874
  });
41353
41875
  } catch (err) {
41354
41876
  throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
41355
41877
  }
41356
41878
  if (!res.ok)
41357
41879
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
41358
- const html = await res.text();
41359
- const results = parseBingHtml(html, maxResults);
41360
- if (results.length === 0)
41361
- throw new Error("Parsed 0 results from Bing");
41362
- return results;
41363
- }
41364
- function parseBingHtml(html, max) {
41365
- const results = [];
41366
- const algoRegex = /<li[^>]*class="b_algo"[^>]*>([\s\S]*?)<\/li>/g;
41367
- let match2;
41368
- while ((match2 = algoRegex.exec(html)) !== null) {
41369
- if (results.length >= max)
41370
- break;
41371
- const item = match2[1];
41372
- const linkMatch = item.match(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/i);
41373
- if (!linkMatch)
41374
- continue;
41375
- const url = linkMatch[1];
41376
- const title = stripHtml(linkMatch[2]);
41377
- if (!url || !title)
41378
- continue;
41379
- const snippetMatch = item.match(/<p[^>]*>([\s\S]*?)<\/p>/i);
41380
- const snippet = snippetMatch ? stripHtml(snippetMatch[1]) : "";
41381
- results.push({ title, url, snippet });
41382
- }
41383
- 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
+ }));
41384
41887
  }
41385
41888
  async function searchDuckDuckGo(query2, maxResults) {
41386
41889
  const encoded = encodeURIComponent(query2);
@@ -41470,7 +41973,7 @@ function parseDDGHtml(html, maxResults) {
41470
41973
  function stripHtml(html) {
41471
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();
41472
41975
  }
41473
- var SEARCH_TIMEOUT_MS, _dispatcher, WebSearchTool, BING_UA, DDG_UA, DDG_ENDPOINTS;
41976
+ var SEARCH_TIMEOUT_MS, _dispatcher, WebSearchTool, DDG_UA, DDG_ENDPOINTS;
41474
41977
  var init_web_search = __esm({
41475
41978
  "../core/dist/tools/web-search.js"() {
41476
41979
  "use strict";
@@ -41499,7 +42002,7 @@ var init_web_search = __esm({
41499
42002
  const backends = [
41500
42003
  { name: "Serper", fn: searchSerper },
41501
42004
  { name: "Brave", fn: searchBrave },
41502
- { name: "Bing", fn: searchBing },
42005
+ { name: "Bocha", fn: searchBocha },
41503
42006
  { name: "DuckDuckGo", fn: searchDuckDuckGo }
41504
42007
  ];
41505
42008
  const errors = [];
@@ -41531,7 +42034,6 @@ var init_web_search = __esm({
41531
42034
  });
41532
42035
  }
41533
42036
  };
41534
- 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";
41535
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";
41536
42038
  DDG_ENDPOINTS = [
41537
42039
  "https://lite.duckduckgo.com/lite/",
@@ -42106,7 +42608,7 @@ var init_patch = __esm({
42106
42608
  });
42107
42609
 
42108
42610
  // ../core/dist/tools/process-manager.js
42109
- import { spawn as spawn2 } from "node:child_process";
42611
+ import { spawn as spawn3 } from "node:child_process";
42110
42612
  import { resolve as resolve7 } from "node:path";
42111
42613
  function onBackgroundCompletion(cb) {
42112
42614
  completionListeners.push(cb);
@@ -42174,7 +42676,7 @@ function createBackgroundExecTool(workspacePath) {
42174
42676
  return JSON.stringify({ status: "denied", error: "Working directory must be within workspace" });
42175
42677
  }
42176
42678
  const id = `bg_${++sessionCounter}_${Date.now()}`;
42177
- const child = spawn2("sh", ["-c", command], {
42679
+ const child = spawn3("sh", ["-c", command], {
42178
42680
  cwd: effectiveCwd,
42179
42681
  stdio: ["ignore", "pipe", "pipe"]
42180
42682
  });
@@ -43392,6 +43894,8 @@ var init_attention = __esm({
43392
43894
  triageToolHandlers;
43393
43895
  triageChatFn;
43394
43896
  lastTriageResult;
43897
+ /** True while a full-session deliberation is in progress (suppresses yield points). */
43898
+ isDeliberating = false;
43395
43899
  /** Tracks whether the last yield-point decision was 'cancel' vs 'preempt'. */
43396
43900
  lastYieldDecision;
43397
43901
  unsubscribeNewItem;
@@ -43534,50 +44038,22 @@ var init_attention = __esm({
43534
44038
  }
43535
44039
  }
43536
44040
  let triaged = false;
43537
- if (this.mailbox.depth > 0 && this.triageJudge && this.needsLLMTriage(item)) {
43538
- const triageResult = await this.performTriage(item);
43539
- if (triageResult) {
43540
- triaged = true;
43541
- if (triageResult.processItemId !== item.id) {
43542
- this.mailbox.putBack(item);
43543
- const chosen = this.mailbox.dequeueById(triageResult.processItemId);
43544
- if (chosen) {
43545
- item = chosen;
43546
- } else {
43547
- const redequeued = this.mailbox.dequeueById(item.id);
43548
- if (redequeued)
43549
- item = redequeued;
43550
- }
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);
43551
44050
  }
43552
- const queueSnapshot = this.mailbox.getQueuedItems();
43553
- const isProtected = (id) => {
43554
- const it = queueSnapshot.find((i) => i.id === id);
43555
- if (!it)
43556
- return false;
43557
- if (it.sourceType === "human_chat")
43558
- return true;
43559
- if (it.payload.extra?.triggerExecution)
43560
- return true;
43561
- return false;
43562
- };
43563
- for (const deferId of triageResult.deferItemIds) {
43564
- if (isProtected(deferId))
43565
- continue;
43566
- this.mailbox.defer(deferId);
43567
- }
43568
- for (const dropId of triageResult.dropItemIds) {
43569
- if (isProtected(dropId))
43570
- continue;
43571
- 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);
43572
44056
  }
43573
- this.lastTriageResult = { ...triageResult, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
43574
- this.delegate?.onTriageCompleted?.(triageResult);
43575
- this.eventBus.emit("attention:triage", {
43576
- agentId: this.agentId,
43577
- triage: this.lastTriageResult
43578
- });
43579
- const triageDecision = this.recordDecision("triage", item, triageResult.reasoning);
43580
- this.delegate?.onDecisionMade(triageDecision);
43581
44057
  }
43582
44058
  }
43583
44059
  if (!triaged) {
@@ -43744,6 +44220,9 @@ var init_attention = __esm({
43744
44220
  * - 'merge' → item was absorbed into current work (caller may inject into session)
43745
44221
  */
43746
44222
  async checkYieldPoint() {
44223
+ if (this.isDeliberating) {
44224
+ return { decision: "continue" };
44225
+ }
43747
44226
  if (!this.interruptSignal || !this.currentFocus) {
43748
44227
  return { decision: "continue" };
43749
44228
  }
@@ -43959,8 +44438,10 @@ var init_attention = __esm({
43959
44438
  description: t.description,
43960
44439
  inputSchema: t.inputSchema
43961
44440
  }));
44441
+ const triageAgentName = ctx?.agentName ?? "Agent";
44442
+ const triageRoleHint = ctx?.agentRole ? ` Your role: ${ctx.agentRole}.` : "";
43962
44443
  const messages = [
43963
- { 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 {` },
43964
44445
  { role: "user", content: prompt }
43965
44446
  ];
43966
44447
  let iterations = 0;
@@ -44028,6 +44509,120 @@ var init_attention = __esm({
44028
44509
  return null;
44029
44510
  }
44030
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
+ }
44031
44626
  buildTriagePrompt(headItem, ctx) {
44032
44627
  const agentName = ctx?.agentName ?? "Agent";
44033
44628
  const queuedItems = this.mailbox.getQueuedItems();
@@ -44149,6 +44744,7 @@ var init_attention = __esm({
44149
44744
  processedItemId: this.lastTriageResult.processItemId,
44150
44745
  deferredItemIds: this.lastTriageResult.deferItemIds,
44151
44746
  droppedItemIds: this.lastTriageResult.dropItemIds,
44747
+ inlineCompletedIds: this.lastTriageResult.inlineCompletedIds ?? [],
44152
44748
  timestamp: this.lastTriageResult.timestamp
44153
44749
  } : void 0
44154
44750
  };
@@ -44952,7 +45548,8 @@ var init_tool_loop_detector = __esm({
44952
45548
  detectors: {
44953
45549
  genericRepeat: true,
44954
45550
  pingPong: true,
44955
- noProgress: true
45551
+ noProgress: true,
45552
+ sameToolBurst: true
44956
45553
  }
44957
45554
  };
44958
45555
  ToolLoopDetector = class {
@@ -44991,6 +45588,11 @@ var init_tool_loop_detector = __esm({
44991
45588
  if (result.detected)
44992
45589
  return result;
44993
45590
  }
45591
+ if (this.config.detectors.sameToolBurst) {
45592
+ const result = this.detectSameToolBurst();
45593
+ if (result.detected)
45594
+ return result;
45595
+ }
44994
45596
  return { detected: false, severity: "none", pattern: "", message: "" };
44995
45597
  }
44996
45598
  /**
@@ -45085,6 +45687,38 @@ var init_tool_loop_detector = __esm({
45085
45687
  }
45086
45688
  return noDetection();
45087
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
+ }
45088
45722
  reset() {
45089
45723
  this.history = [];
45090
45724
  }
@@ -45314,6 +45948,7 @@ var init_agent2 = __esm({
45314
45948
  semanticSearch;
45315
45949
  currentSessionId;
45316
45950
  currentInteractingUserId;
45951
+ pendingDeliberationResult;
45317
45952
  dbSessionMap = /* @__PURE__ */ new Map();
45318
45953
  orgContext;
45319
45954
  contextMdPath;
@@ -45722,6 +46357,12 @@ ${notification.stdoutTail}`);
45722
46357
  getAttentionController() {
45723
46358
  return this.attentionController;
45724
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
+ }
45725
46366
  /**
45726
46367
  * Check yield point during task execution — called between LLM turns.
45727
46368
  * Returns decision info so the tool loop can act on preemption or merges.
@@ -45770,12 +46411,20 @@ ${notification.stdoutTail}`);
45770
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) })) : [];
45771
46412
  const activities = this.recentActivitySummaries();
45772
46413
  const activeTaskIds = Array.from(this.activeTasks);
45773
- 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 };
45774
46415
  },
45775
46416
  onTriageCompleted: (result) => {
45776
46417
  if (!result)
45777
46418
  return;
45778
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);
45779
46428
  }
45780
46429
  };
45781
46430
  }
@@ -45874,7 +46523,7 @@ ${notification.stdoutTail}`);
45874
46523
  case "requirement_update": {
45875
46524
  if (extra.actionRequired) {
45876
46525
  const reqId = item.payload.requirementId ?? "unknown";
45877
- 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" }));
45878
46527
  resolveResponse(reply);
45879
46528
  return reply;
45880
46529
  }
@@ -45887,7 +46536,7 @@ ${notification.stdoutTail}`);
45887
46536
  }
45888
46537
  case "requirement_comment": {
45889
46538
  const reqId = item.payload.requirementId ?? "unknown";
45890
- 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" }));
45891
46540
  resolveResponse(reply);
45892
46541
  return reply;
45893
46542
  }
@@ -45899,7 +46548,7 @@ ${notification.stdoutTail}`);
45899
46548
  this.injectUserMessage(sessionId, item.payload.content);
45900
46549
  } else {
45901
46550
  const commentTaskId = taskId2 ?? "unknown";
45902
- 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" }));
45903
46552
  }
45904
46553
  resolveResponse("");
45905
46554
  return;
@@ -46407,7 +47056,7 @@ ${conversationText}`
46407
47056
  const filename = `${toolName}_${++this.toolResultCounter}_${Date.now()}.txt`;
46408
47057
  const filepath = join9(offloadDir, filename);
46409
47058
  writeFileSync8(filepath, result);
46410
- const preview = result.slice(0, previewSize);
47059
+ const preview = safeSlice(result, 0, previewSize);
46411
47060
  const lineCount = result.split("\n").length;
46412
47061
  return [
46413
47062
  `[FULL output (${result.length} chars, ${lineCount} lines) saved to: ${filepath}]`,
@@ -46416,11 +47065,11 @@ ${conversationText}`
46416
47065
  ``,
46417
47066
  preview,
46418
47067
  ``,
46419
- `[... remaining ${result.length - previewSize} chars in file ...]`
47068
+ `[... remaining ${result.length - preview.length} chars in file ...]`
46420
47069
  ].join("\n");
46421
47070
  } catch {
46422
47071
  const fallbackSize = isBrowserTool ? 3e4 : 8e3;
46423
- return result.slice(0, fallbackSize) + `
47072
+ return safeSlice(result, 0, fallbackSize) + `
46424
47073
 
46425
47074
  [... output truncated at ${fallbackSize} of ${result.length} total chars due to file-save failure ...]`;
46426
47075
  }
@@ -46635,6 +47284,109 @@ ${conversationText}`
46635
47284
  lines.push(`Reasoning: ${result.reasoning}`);
46636
47285
  this.currentCognition = lines.join("\n");
46637
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
+ }
46638
47390
  getDynamicContext() {
46639
47391
  const parts = [...this.dynamicContextProviders.values()].map((p) => p()).filter(Boolean);
46640
47392
  for (const [name, instructions] of this.activatedSkillInstructions) {
@@ -46718,6 +47470,12 @@ ${instructions}
46718
47470
  getUsageStats() {
46719
47471
  return this.metricsCollector.getUsageStats();
46720
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
+ }
46721
47479
  emitAudit(event) {
46722
47480
  this.metricsCollector.recordAudit(event);
46723
47481
  this.auditCallback?.(event);
@@ -47136,12 +47894,16 @@ ${block}
47136
47894
  type: "llm_request",
47137
47895
  action: "chat",
47138
47896
  tokensUsed: tokensThisCall,
47897
+ inputTokens: response.usage.inputTokens,
47898
+ outputTokens: response.usage.outputTokens,
47899
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47139
47900
  durationMs: Date.now() - llmStart,
47140
47901
  success: true
47141
47902
  });
47142
47903
  let toolIterations = 0;
47143
47904
  const effectiveMaxIter = options?.maxToolIterations ?? this._maxToolIterations;
47144
47905
  const commentToolUsed = /* @__PURE__ */ new Set();
47906
+ const requirementActionToolUsed = /* @__PURE__ */ new Set();
47145
47907
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
47146
47908
  if (++toolIterations > effectiveMaxIter) {
47147
47909
  log17.warn("Tool loop hit max iterations", {
@@ -47242,6 +48004,10 @@ ${block}
47242
48004
  if (tc.name === "task_comment" || tc.name === "requirement_comment") {
47243
48005
  commentToolUsed.add(tc.name);
47244
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
+ }
47245
48011
  }
47246
48012
  const loopCheck = this.loopDetector.check();
47247
48013
  if (loopCheck.detected) {
@@ -47310,6 +48076,9 @@ ${chatYield.item.payload.content}`;
47310
48076
  type: "llm_request",
47311
48077
  action: "chat",
47312
48078
  tokensUsed: tokens2,
48079
+ inputTokens: response.usage.inputTokens,
48080
+ outputTokens: response.usage.outputTokens,
48081
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47313
48082
  durationMs: Date.now() - llmStart2,
47314
48083
  success: true
47315
48084
  });
@@ -47401,6 +48170,92 @@ ${chatYield.item.payload.content}`;
47401
48170
  }, this.getEffectiveProvider()), "Chat LLM comment-reminder-final");
47402
48171
  }
47403
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
+ }
47404
48259
  const rawReply = sanitizeLLMReply(response.content);
47405
48260
  const displayReply = stripCompletionMarker(rawReply);
47406
48261
  const outputCheck = await this.guardrails.checkOutput(displayReply, { agentId: this.id });
@@ -47569,6 +48424,9 @@ ${chatYield.item.payload.content}`;
47569
48424
  type: "llm_request",
47570
48425
  action: "chat_stream",
47571
48426
  tokensUsed: tokensThisCall,
48427
+ inputTokens: response.usage.inputTokens,
48428
+ outputTokens: response.usage.outputTokens,
48429
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47572
48430
  durationMs: Date.now() - llmStart,
47573
48431
  success: true
47574
48432
  });
@@ -47747,6 +48605,9 @@ ${streamYield.item.payload.content}`;
47747
48605
  type: "llm_request",
47748
48606
  action: "chat_stream",
47749
48607
  tokensUsed: tokens2,
48608
+ inputTokens: response.usage.inputTokens,
48609
+ outputTokens: response.usage.outputTokens,
48610
+ cost: this.computeCallCost(response.usage.inputTokens, response.usage.outputTokens),
47750
48611
  durationMs: Date.now() - llmStart2,
47751
48612
  success: true
47752
48613
  });
@@ -48052,7 +48913,7 @@ ${streamYield.item.payload.content}`;
48052
48913
  let taskLlmTokens = response.usage.inputTokens + response.usage.outputTokens;
48053
48914
  this.updateTokensUsed(taskLlmTokens);
48054
48915
  this.calibrateTokenCounter(response.usage.inputTokens);
48055
- 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 });
48056
48917
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
48057
48918
  taskToolIterations++;
48058
48919
  if (cancelToken?.cancelled) {
@@ -48244,7 +49105,7 @@ ${yieldResult.item.payload.content}`
48244
49105
  taskLlmTokens = response.usage.inputTokens + response.usage.outputTokens;
48245
49106
  this.updateTokensUsed(taskLlmTokens);
48246
49107
  this.calibrateTokenCounter(response.usage.inputTokens);
48247
- 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 });
48248
49109
  }
48249
49110
  if (cancelToken?.cancelled) {
48250
49111
  flushText();
@@ -48484,7 +49345,7 @@ ${yieldResult.item.payload.content}`
48484
49345
  let risTokens = response.usage.inputTokens + response.usage.outputTokens;
48485
49346
  this.updateTokensUsed(risTokens);
48486
49347
  this.calibrateTokenCounter(response.usage.inputTokens);
48487
- 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 });
48488
49349
  let toolIter = 0;
48489
49350
  while (response.finishReason === "tool_use" && response.toolCalls?.length || response.finishReason === "max_tokens") {
48490
49351
  if (++toolIter > 200)
@@ -48513,6 +49374,7 @@ ${yieldResult.item.payload.content}`
48513
49374
  continue;
48514
49375
  }
48515
49376
  emit("tool_start", tc.name, { arguments: tc.arguments });
49377
+ this.emitActivityLog(actId, "tool_start", tc.name, { arguments: tc.arguments });
48516
49378
  const toolStart = Date.now();
48517
49379
  try {
48518
49380
  let result = await this.executeTool(tc, void 0, sessionId);
@@ -48520,11 +49382,13 @@ ${yieldResult.item.payload.content}`
48520
49382
  const isErr = isErrorResult2(result);
48521
49383
  const durationMs = Date.now() - toolStart;
48522
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 });
48523
49386
  this.emitAudit({ type: "tool_call", action: tc.name, durationMs, success: !isErr });
48524
49387
  this.memory.appendMessage(sessionId, { role: "tool", content: result, toolCallId: tc.id });
48525
49388
  } catch (toolErr) {
48526
49389
  const durationMs = Date.now() - toolStart;
48527
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) });
48528
49392
  this.emitAudit({ type: "tool_call", action: tc.name, durationMs, success: false });
48529
49393
  this.memory.appendMessage(sessionId, { role: "tool", content: `Error: ${String(toolErr)}`, toolCallId: tc.id });
48530
49394
  }
@@ -48546,11 +49410,14 @@ ${yieldResult.item.payload.content}`
48546
49410
  risTokens = response.usage.inputTokens + response.usage.outputTokens;
48547
49411
  this.updateTokensUsed(risTokens);
48548
49412
  this.calibrateTokenCounter(response.usage.inputTokens);
48549
- 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 });
48550
49414
  }
48551
49415
  flushText();
48552
49416
  const rawReply = sanitizeLLMReply(response.content);
48553
49417
  const displayReply = stripCompletionMarker(rawReply);
49418
+ if (displayReply.trim()) {
49419
+ this.emitActivityLog(actId, "text", displayReply);
49420
+ }
48554
49421
  this.memory.appendMessage(sessionId, { role: "assistant", content: displayReply, reasoningContent: response.reasoningContent });
48555
49422
  return rawReply;
48556
49423
  } catch (error) {
@@ -48834,9 +49701,20 @@ ${d.text}
48834
49701
  const explicitTaskId = toolCall.arguments.related_task_id;
48835
49702
  const taskId2 = explicitTaskId ?? this.getCurrentTaskId();
48836
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(", ")} -->` : "";
48837
49715
  const formattedMsg = `**${title}**
48838
49716
 
48839
- ${body}`;
49717
+ ${body}${contextSuffix}`;
48840
49718
  if (this.currentSessionId) {
48841
49719
  this.memory.appendMessage(this.currentSessionId, {
48842
49720
  role: "assistant",
@@ -48850,7 +49728,7 @@ ${body}`;
48850
49728
  this.eventBus.emit("agent:notify-user", {
48851
49729
  agentId: this.id,
48852
49730
  sessionId: this.currentSessionId,
48853
- targetUserId: this.currentInteractingUserId,
49731
+ targetUserId: explicitTargetUser ?? this.currentInteractingUserId,
48854
49732
  title,
48855
49733
  body,
48856
49734
  priority,
@@ -48903,6 +49781,21 @@ ${body}`;
48903
49781
  return JSON.stringify({ status: "error", message: `Failed to get user approval: ${String(err)}` });
48904
49782
  }
48905
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
+ }
48906
49799
  const profile = this.config.profile;
48907
49800
  if (profile) {
48908
49801
  if (profile.toolWhitelist && !profile.toolWhitelist.includes(toolCall.name)) {
@@ -49174,6 +50067,9 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
49174
50067
  "**Report format** \u2014 create a deliverable via `deliverable_create`:",
49175
50068
  `- **title**: "Daily Report \u2014 ${todayDate}"`,
49176
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',
49177
50073
  "- **Content must be concise, clear, and accurate**:",
49178
50074
  " 1. **My work today**: What you personally accomplished (tasks reviewed, approved/rejected, decisions made)",
49179
50075
  " 2. **My team progress**: What each member of YOUR team accomplished today (use `team_status` to check). Focus on your own team only.",
@@ -49880,21 +50776,52 @@ ${sharedContent}` : roleContent;
49880
50776
  });
49881
50777
 
49882
50778
  // ../core/dist/tools/mcp-client.js
49883
- import { spawn as spawn3 } from "node:child_process";
49884
- var log18, MCPClientManager;
50779
+ import { spawn as spawn4 } from "node:child_process";
50780
+ var log18, DEFAULT_IDLE_TIMEOUT_MS, MCPClientManager;
49885
50781
  var init_mcp_client = __esm({
49886
50782
  "../core/dist/tools/mcp-client.js"() {
49887
50783
  "use strict";
49888
50784
  init_dist();
49889
50785
  log18 = createLogger("mcp-client");
50786
+ DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
49890
50787
  MCPClientManager = class _MCPClientManager {
49891
50788
  servers = /* @__PURE__ */ new Map();
50789
+ serverConfigs = /* @__PURE__ */ new Map();
49892
50790
  requestId = 0;
49893
50791
  pendingRequests = /* @__PURE__ */ new Map();
49894
50792
  stdoutBuffers = /* @__PURE__ */ new Map();
50793
+ idleTimers = /* @__PURE__ */ new Map();
50794
+ idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS;
49895
50795
  static scopedKey(name, scopeId) {
49896
50796
  return `${name}::${scopeId}`;
49897
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
+ }
49898
50825
  /**
49899
50826
  * Spawn an MCP server process, run JSON-RPC init handshake, list tools,
49900
50827
  * and store under the given internal key. Shared by connect and connectScoped.
@@ -49905,8 +50832,9 @@ var init_mcp_client = __esm({
49905
50832
  log18.info(`MCP server ${displayName} already connected, reusing (${existing.tools.length} tools)`);
49906
50833
  return existing.tools;
49907
50834
  }
50835
+ this.serverConfigs.set(key2, { displayName, config });
49908
50836
  log18.info(`Connecting to MCP server: ${displayName}`, { command: config.command, key: key2 });
49909
- const proc = spawn3(config.command, config.args ?? [], {
50837
+ const proc = spawn4(config.command, config.args ?? [], {
49910
50838
  stdio: ["pipe", "pipe", "pipe"],
49911
50839
  env: { ...process.env, ...config.env },
49912
50840
  shell: process.platform === "win32"
@@ -49931,6 +50859,7 @@ var init_mcp_client = __esm({
49931
50859
  }
49932
50860
  this.servers.delete(key2);
49933
50861
  this.stdoutBuffers.delete(proc);
50862
+ this.clearIdleTimer(key2);
49934
50863
  for (const [id, req] of this.pendingRequests) {
49935
50864
  if (req.proc !== proc)
49936
50865
  continue;
@@ -49951,6 +50880,7 @@ var init_mcp_client = __esm({
49951
50880
  const toolsResult = await this.sendRequest(proc, "tools/list", {});
49952
50881
  const tools = toolsResult?.tools ?? [];
49953
50882
  this.servers.set(key2, { process: proc, tools });
50883
+ this.resetIdleTimer(key2);
49954
50884
  log18.info(`MCP server ${displayName} connected with ${tools.length} tools`);
49955
50885
  return tools;
49956
50886
  } catch (err) {
@@ -49970,19 +50900,27 @@ var init_mcp_client = __esm({
49970
50900
  const key2 = _MCPClientManager.scopedKey(name, scopeId);
49971
50901
  return this.connectByKey(key2, `${name}[${scopeId}]`, config);
49972
50902
  }
49973
- callToolByKey(key2, toolName, args) {
49974
- const server = this.servers.get(key2);
49975
- if (!server)
49976
- throw new Error(`MCP server not found: ${key2}`);
49977
- 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", {
49978
50917
  name: toolName,
49979
50918
  arguments: args
49980
- }).then((result) => {
49981
- const content = result?.content;
49982
- if (content?.[0]?.text)
49983
- return content[0].text;
49984
- return JSON.stringify(result);
49985
50919
  });
50920
+ const content = result?.content;
50921
+ if (content?.[0]?.text)
50922
+ return content[0].text;
50923
+ return JSON.stringify(result);
49986
50924
  }
49987
50925
  async callTool(serverName, toolName, args) {
49988
50926
  return this.callToolByKey(serverName, toolName, args);
@@ -50023,6 +50961,7 @@ var init_mcp_client = __esm({
50023
50961
  async disconnectServer(name) {
50024
50962
  const server = this.servers.get(name);
50025
50963
  if (server) {
50964
+ this.clearIdleTimer(name);
50026
50965
  server.process.kill();
50027
50966
  this.servers.delete(name);
50028
50967
  this.stdoutBuffers.delete(server.process);
@@ -50036,6 +50975,7 @@ var init_mcp_client = __esm({
50036
50975
  /**
50037
50976
  * Disconnect all scoped server instances belonging to a given scope (e.g. agent).
50038
50977
  * Shared (non-scoped) servers are not affected.
50978
+ * The server configs are retained so the server can auto-reconnect on next tool call.
50039
50979
  */
50040
50980
  async disconnectAllForScope(scopeId) {
50041
50981
  const suffix = `::${scopeId}`;
@@ -50045,10 +50985,28 @@ var init_mcp_client = __esm({
50045
50985
  }
50046
50986
  }
50047
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
+ }
50048
51005
  async disconnectAll() {
50049
51006
  for (const name of [...this.servers.keys()]) {
50050
51007
  await this.disconnectServer(name);
50051
51008
  }
51009
+ this.serverConfigs.clear();
50052
51010
  }
50053
51011
  /**
50054
51012
  * Accumulates stdout data into a line buffer and dispatches
@@ -52664,6 +53622,10 @@ function createProjectTools(ctx) {
52664
53622
  type: "string",
52665
53623
  description: "Path to the file or directory that contains the actual content (must already exist)"
52666
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
+ },
52667
53629
  tags: { type: "string", description: "Comma-separated tags for discoverability" }
52668
53630
  },
52669
53631
  required: ["type", "title", "summary"]
@@ -52675,6 +53637,7 @@ function createProjectTools(ctx) {
52675
53637
  title: args["title"],
52676
53638
  summary: args["summary"],
52677
53639
  reference: args["reference"],
53640
+ format: args["format"],
52678
53641
  tags: args["tags"]
52679
53642
  });
52680
53643
  const resp = {
@@ -53961,6 +54924,9 @@ var init_agent_manager = __esm({
53961
54924
  escalationHandler;
53962
54925
  approvalHandler;
53963
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;
53964
54930
  activityCallbacks;
53965
54931
  recallCallbacks;
53966
54932
  delegationManager;
@@ -54027,6 +54993,7 @@ var init_agent_manager = __esm({
54027
54993
  title: opts.title,
54028
54994
  summary: opts.summary,
54029
54995
  reference: opts.reference,
54996
+ format: opts.format,
54030
54997
  tags,
54031
54998
  agentId: agentId2,
54032
54999
  projectId
@@ -54823,9 +55790,7 @@ Known issues: ${knownIssues}` : ""}`
54823
55790
  const ah = this.approvalHandler;
54824
55791
  agent.setApprovalCallback(async (req) => ah(id, req));
54825
55792
  }
54826
- if (this.stateChangeHandler) {
54827
- agent.setStateChangeCallback(this.stateChangeHandler);
54828
- }
55793
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
54829
55794
  if (this.activityCallbacks) {
54830
55795
  agent.setActivityCallbacks(this.activityCallbacks);
54831
55796
  }
@@ -55374,9 +56339,7 @@ Known issues: ${knownIssues}` : ""}`
55374
56339
  const ah = this.approvalHandler;
55375
56340
  agent.setApprovalCallback(async (req) => ah(id, req));
55376
56341
  }
55377
- if (this.stateChangeHandler) {
55378
- agent.setStateChangeCallback(this.stateChangeHandler);
55379
- }
56342
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
55380
56343
  if (this.activityCallbacks) {
55381
56344
  agent.setActivityCallbacks(this.activityCallbacks);
55382
56345
  }
@@ -55440,6 +56403,9 @@ Known issues: ${knownIssues}` : ""}`
55440
56403
  async stopAgent(agentId2) {
55441
56404
  const agent = this.getAgent(agentId2);
55442
56405
  await agent.stop();
56406
+ this.cancelMcpRelease(agentId2);
56407
+ this.browserSessionManager.cleanupAgent(agentId2);
56408
+ await this.mcpManager.removeAllForScope(agentId2);
55443
56409
  }
55444
56410
  async removeAgent(agentId2, opts) {
55445
56411
  const agent = this.agents.get(agentId2);
@@ -55448,8 +56414,9 @@ Known issues: ${knownIssues}` : ""}`
55448
56414
  await agent.stop();
55449
56415
  } catch {
55450
56416
  }
56417
+ this.cancelMcpRelease(agentId2);
55451
56418
  this.browserSessionManager.cleanupAgent(agentId2);
55452
- await this.mcpManager.disconnectAllForScope(agentId2);
56419
+ await this.mcpManager.removeAllForScope(agentId2);
55453
56420
  this.delegationManager.unregisterAgentCard(agentId2);
55454
56421
  this.agents.delete(agentId2);
55455
56422
  this.eventBus.emit("agent:removed", { agentId: agentId2 });
@@ -55517,13 +56484,49 @@ Known issues: ${knownIssues}` : ""}`
55517
56484
  agent.setApprovalCallback(async (req) => handler4(id, req));
55518
56485
  }
55519
56486
  }
55520
- setStateChangeHandler(handler4) {
55521
- this.stateChangeHandler = (agentId2, state) => {
55522
- 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
+ }
55523
56500
  this.delegationManager.updateAgentStatus(agentId2, state.status);
56501
+ if (this.stateChangeHandler) {
56502
+ this.stateChangeHandler(agentId2, state);
56503
+ }
55524
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;
55525
56528
  for (const [, agent] of this.agents) {
55526
- agent.setStateChangeCallback(this.stateChangeHandler);
56529
+ agent.setStateChangeCallback(this.buildStateChangeCallback());
55527
56530
  }
55528
56531
  }
55529
56532
  /**
@@ -55544,6 +56547,10 @@ Known issues: ${knownIssues}` : ""}`
55544
56547
  "agent:resumed",
55545
56548
  "agent:focus-changed",
55546
56549
  "agent:message",
56550
+ "agent:notify-user",
56551
+ "agent:escalation",
56552
+ "agent:activity-log",
56553
+ "agent:activity_log",
55547
56554
  "task:completed",
55548
56555
  "task:failed",
55549
56556
  "mailbox:new-item",
@@ -55781,6 +56788,7 @@ Known issues: ${knownIssues}` : ""}`
55781
56788
  } catch {
55782
56789
  }
55783
56790
  }
56791
+ await this.mcpManager.disconnectAll();
55784
56792
  log32.info("AgentManager shutdown complete \u2014 all metrics flushed");
55785
56793
  }
55786
56794
  // ─── Role Template Versioning & Sync ──────────────────────────────────────
@@ -56198,7 +57206,7 @@ var init_anthropic = __esm({
56198
57206
  {
56199
57207
  type: "tool_result",
56200
57208
  tool_use_id: m.toolCallId ?? "",
56201
- content: getTextContent(m.content)
57209
+ content: sanitizeForLLM(getTextContent(m.content))
56202
57210
  }
56203
57211
  ]
56204
57212
  };
@@ -56391,7 +57399,7 @@ var init_openai = __esm({
56391
57399
  if (m.role === "tool") {
56392
57400
  return {
56393
57401
  role: "tool",
56394
- content: getTextContent(m.content),
57402
+ content: sanitizeForLLM(getTextContent(m.content)),
56395
57403
  tool_call_id: m.toolCallId ?? ""
56396
57404
  };
56397
57405
  }
@@ -56795,7 +57803,7 @@ var init_google = __esm({
56795
57803
  parts: [{
56796
57804
  functionResponse: {
56797
57805
  name: msg.toolCallId ?? "unknown",
56798
- response: { result: getTextContent(msg.content) }
57806
+ response: { result: sanitizeForLLM(getTextContent(msg.content)) }
56799
57807
  }
56800
57808
  }]
56801
57809
  });
@@ -57017,7 +58025,7 @@ var init_ollama = __esm({
57017
58025
  convertMessages(messages) {
57018
58026
  return messages.map((m) => {
57019
58027
  if (m.role === "tool") {
57020
- return { role: "tool", content: getTextContent(m.content) };
58028
+ return { role: "tool", content: sanitizeForLLM(getTextContent(m.content)) };
57021
58029
  }
57022
58030
  if (m.toolCalls?.length) {
57023
58031
  return {
@@ -57253,9 +58261,9 @@ var init_auth_profiles = __esm({
57253
58261
 
57254
58262
  // ../core/dist/llm/oauth-manager.js
57255
58263
  import { createServer } from "node:http";
57256
- import { randomBytes as randomBytes2, createHash } from "node:crypto";
58264
+ import { randomBytes as randomBytes3, createHash } from "node:crypto";
57257
58265
  function generatePKCE() {
57258
- const verifier = randomBytes2(32).toString("base64url");
58266
+ const verifier = randomBytes3(32).toString("base64url");
57259
58267
  const challenge = createHash("sha256").update(verifier).digest("base64url");
57260
58268
  return { verifier, challenge };
57261
58269
  }
@@ -57336,7 +58344,7 @@ var init_oauth_manager = __esm({
57336
58344
  throw new Error(`Unknown OAuth provider: ${provider}`);
57337
58345
  this.cancelPendingLogin(provider);
57338
58346
  const { verifier, challenge } = generatePKCE();
57339
- const state = randomBytes2(16).toString("hex");
58347
+ const state = randomBytes3(16).toString("hex");
57340
58348
  const port = config.callbackPort ?? 1455;
57341
58349
  const redirectUri = `http://localhost:${port}/auth/callback`;
57342
58350
  const promise = new Promise((resolve20, reject) => {
@@ -57625,6 +58633,7 @@ var init_router = __esm({
57625
58633
  autoSelect = false;
57626
58634
  providerTiers = [];
57627
58635
  fallbackOrder = [];
58636
+ _autoFallback = true;
57628
58637
  /** Health tracked per model: key = "providerName:modelId" */
57629
58638
  modelHealth = /* @__PURE__ */ new Map();
57630
58639
  /** Provider-level degradation for non-retryable (auth/billing) errors */
@@ -57913,6 +58922,12 @@ var init_router = __esm({
57913
58922
  setFallbackOrder(order) {
57914
58923
  this.fallbackOrder = order.filter((n2) => this.providers.has(n2));
57915
58924
  }
58925
+ get autoFallback() {
58926
+ return this._autoFallback;
58927
+ }
58928
+ setAutoFallback(enabled) {
58929
+ this._autoFallback = enabled;
58930
+ }
57916
58931
  static assessComplexity(request) {
57917
58932
  const totalChars = request.messages.reduce((s2, m) => s2 + getTextContent(m.content).length, 0);
57918
58933
  const toolCount = request.tools?.length ?? 0;
@@ -57964,6 +58979,8 @@ var init_router = __esm({
57964
58979
  return this.defaultProvider;
57965
58980
  }
57966
58981
  getFallbacks(primary) {
58982
+ if (!this._autoFallback)
58983
+ return [];
57967
58984
  const order = this.fallbackOrder.length > 0 ? this.fallbackOrder : [...this.providers.keys()];
57968
58985
  return order.filter((n2) => n2 !== primary && this.isAvailable(n2));
57969
58986
  }
@@ -58082,7 +59099,7 @@ var init_router = __esm({
58082
59099
  } catch (error) {
58083
59100
  lastError = error;
58084
59101
  log35.error(`LLM request failed for ${primary}:${provider.model}`, { error: String(error) });
58085
- if (!_LLMRouter.isNonRetryableError(error)) {
59102
+ if (this._autoFallback && !_LLMRouter.isNonRetryableError(error)) {
58086
59103
  const altModel = this.findHealthyModel(primary);
58087
59104
  if (altModel && altModel !== provider.model) {
58088
59105
  log35.info(`Trying alternate model ${altModel} on ${primary}`);
@@ -58173,7 +59190,7 @@ var init_router = __esm({
58173
59190
  throw lastError;
58174
59191
  }
58175
59192
  log35.error(`LLM stream request failed for ${primary}:${provider.model}`, { error: String(error) });
58176
- if (!_LLMRouter.isNonRetryableError(error)) {
59193
+ if (this._autoFallback && !_LLMRouter.isNonRetryableError(error)) {
58177
59194
  const altModel = this.findHealthyModel(primary);
58178
59195
  if (altModel && altModel !== provider.model) {
58179
59196
  log35.info(`Stream: trying alternate model ${altModel} on ${primary}`);
@@ -58304,7 +59321,7 @@ var init_router = __esm({
58304
59321
  };
58305
59322
  }
58306
59323
  }
58307
- return { defaultProvider: this.defaultProvider, providers };
59324
+ return { defaultProvider: this.defaultProvider, autoFallback: this._autoFallback, providers };
58308
59325
  }
58309
59326
  updateProviderModelConfig(providerName, config) {
58310
59327
  this.customModelConfigs.set(providerName, {
@@ -58401,6 +59418,17 @@ var init_router = __esm({
58401
59418
  const provider = this.providers.get(name);
58402
59419
  return provider?.model ?? "";
58403
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
+ }
58404
59432
  getModelInputTypes(providerName) {
58405
59433
  const name = providerName ?? this.defaultProvider;
58406
59434
  const provider = this.providers.get(name);
@@ -58559,7 +59587,7 @@ var init_enhanced_role_loader = __esm({
58559
59587
  });
58560
59588
 
58561
59589
  // ../core/dist/external-gateway.js
58562
- import { createHmac, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
59590
+ import { createHmac, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
58563
59591
  var log37, ExternalAgentGateway, GatewayError;
58564
59592
  var init_external_gateway = __esm({
58565
59593
  "../core/dist/external-gateway.js"() {
@@ -58738,7 +59766,7 @@ var init_external_gateway = __esm({
58738
59766
  return payload;
58739
59767
  }
58740
59768
  async routeMessage(token, message) {
58741
- const messageId = `gwmsg_${Date.now()}_${randomBytes3(4).toString("hex")}`;
59769
+ const messageId = `gwmsg_${Date.now()}_${randomBytes4(4).toString("hex")}`;
58742
59770
  const key2 = this.registrationKey(token.externalAgentId, token.orgId);
58743
59771
  const registration = this.registrations.get(key2);
58744
59772
  if (registration) {
@@ -61352,39 +62380,28 @@ var init_org_service = __esm({
61352
62380
  return p;
61353
62381
  }
61354
62382
  /**
61355
- * Seed a default team ("My Team") for a fresh organization.
61356
- * Creates the team, adds the owner as a human member, and hires a Secretary agent
61357
- * 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.
61358
62386
  */
61359
- async seedDefaultTeam(orgId2, ownerUserId) {
61360
- const existing = this.listTeams(orgId2);
61361
- if (existing.length > 0)
62387
+ async seedDefaultTeam(orgId2, _ownerUserId) {
62388
+ const existingAgents = this.agentManager.listAgents();
62389
+ if (existingAgents.length > 0)
61362
62390
  return;
61363
- log49.info("Seeding default team for org", { orgId: orgId2 });
62391
+ log49.info("Seeding Secretary agent for org", { orgId: orgId2 });
61364
62392
  try {
61365
- const team = await this.createTeam(orgId2, "My Team", "Your primary team \u2014 you and your personal AI Secretary.");
61366
- const owner = this.humans.get(ownerUserId);
61367
- if (owner) {
61368
- this.addMemberToTeam(team.id, ownerUserId, "human");
61369
- }
61370
62393
  const secretary = await this.hireAgent({
61371
62394
  name: "Secretary",
61372
62395
  roleName: "secretary",
61373
62396
  orgId: orgId2,
61374
- teamId: team.id,
61375
- agentRole: "manager",
62397
+ agentRole: "worker",
61376
62398
  skills: ["agent-building", "team-building", "skill-building"]
61377
62399
  });
61378
- await this.updateTeam(team.id, {
61379
- managerId: secretary.id,
61380
- managerType: "agent"
61381
- });
61382
- log49.info("Default team seeded", {
61383
- teamId: team.id,
62400
+ log49.info("Secretary agent seeded", {
61384
62401
  secretaryId: secretary.id
61385
62402
  });
61386
62403
  } catch (error) {
61387
- log49.warn("Failed to seed default team", { error: String(error) });
62404
+ log49.warn("Failed to seed Secretary agent", { error: String(error) });
61388
62405
  }
61389
62406
  }
61390
62407
  static BUILDING_SKILLS = /* @__PURE__ */ new Set(["agent-building", "team-building", "skill-building"]);
@@ -72202,6 +73219,9 @@ ${deliverablesSummary}` : ""
72202
73219
  ],
72203
73220
  allowFreeform: true
72204
73221
  }).then((result) => {
73222
+ const current = this.tasks.get(task.id);
73223
+ if (!current || current.status !== "review")
73224
+ return;
72205
73225
  if (result.approved) {
72206
73226
  this.updateTaskStatus(task.id, "completed", task.reviewerId, false, false, "human", "Review approved");
72207
73227
  } else {
@@ -72470,6 +73490,7 @@ Action: ${guidance}` : ""
72470
73490
  };
72471
73491
  for (const t of tasks)
72472
73492
  statusCounts[t.status]++;
73493
+ const stuckBlockedCount = tasks.filter((t) => t.status === "blocked" && this.areBlockersSatisfied(t)).length;
72473
73494
  const agentMap = /* @__PURE__ */ new Map();
72474
73495
  for (const t of tasks) {
72475
73496
  if (!t.assignedAgentId)
@@ -72502,6 +73523,7 @@ Action: ${guidance}` : ""
72502
73523
  return {
72503
73524
  statusCounts,
72504
73525
  totalTasks: tasks.length,
73526
+ stuckBlockedCount,
72505
73527
  agentWorkload,
72506
73528
  recentActivity,
72507
73529
  averageCompletionTimeMs
@@ -77830,6 +78852,7 @@ ${cleanText}`,
77830
78852
  title: body["title"],
77831
78853
  summary: body["summary"],
77832
78854
  reference: body["reference"],
78855
+ format: body["format"],
77833
78856
  tags: body["tags"],
77834
78857
  taskId: body["taskId"],
77835
78858
  agentId: body["agentId"],
@@ -77857,6 +78880,7 @@ ${cleanText}`,
77857
78880
  title: body["title"],
77858
78881
  summary: body["summary"],
77859
78882
  reference: body["reference"],
78883
+ format: body["format"],
77860
78884
  tags: body["tags"],
77861
78885
  status: body["status"],
77862
78886
  type: body["type"]
@@ -79434,6 +80458,7 @@ EXPLANATION_END`;
79434
80458
  title: body["title"],
79435
80459
  summary: body["summary"] ?? body["content"],
79436
80460
  reference: body["reference"],
80461
+ format: body["format"],
79437
80462
  tags: body["tags"],
79438
80463
  agentId: token.markusAgentId,
79439
80464
  projectId: body["projectId"]
@@ -81142,12 +82167,6 @@ EXPLANATION_END`;
81142
82167
  } catch {
81143
82168
  }
81144
82169
  }
81145
- const billingSummary = this.billingService?.getUsageSummary(orgId2);
81146
- if (billingSummary) {
81147
- llmTokens = Math.max(llmTokens, billingSummary.llmTokens);
81148
- toolCalls = Math.max(toolCalls, billingSummary.toolCalls);
81149
- messages = Math.max(messages, billingSummary.messages);
81150
- }
81151
82170
  this.json(res, 200, {
81152
82171
  usage: {
81153
82172
  orgId: orgId2,
@@ -81155,7 +82174,7 @@ EXPLANATION_END`;
81155
82174
  llmTokens,
81156
82175
  toolCalls,
81157
82176
  messages,
81158
- storageBytes: billingSummary?.storageBytes ?? 0
82177
+ storageBytes: this.billingService?.getUsageSummary(orgId2)?.storageBytes ?? 0
81159
82178
  },
81160
82179
  plan
81161
82180
  });
@@ -81180,7 +82199,8 @@ EXPLANATION_END`;
81180
82199
  requestCount: stats.requestCount,
81181
82200
  toolCalls: stats.toolCalls,
81182
82201
  messages: stats.requestsToday,
81183
- estimatedCost: stats.estimatedCost
82202
+ estimatedCost: stats.estimatedCost,
82203
+ costToday: stats.costToday
81184
82204
  };
81185
82205
  } catch {
81186
82206
  return {
@@ -81195,7 +82215,8 @@ EXPLANATION_END`;
81195
82215
  requestCount: 0,
81196
82216
  toolCalls: 0,
81197
82217
  messages: 0,
81198
- estimatedCost: 0
82218
+ estimatedCost: 0,
82219
+ costToday: 0
81199
82220
  };
81200
82221
  }
81201
82222
  });
@@ -81427,23 +82448,31 @@ EXPLANATION_END`;
81427
82448
  return;
81428
82449
  }
81429
82450
  const body = await this.readBody(req);
81430
- const { defaultProvider } = body;
81431
- if (!defaultProvider) {
81432
- 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" });
81433
82454
  return;
81434
82455
  }
81435
82456
  try {
81436
- 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
+ }
81437
82466
  try {
81438
- saveConfig({ llm: { defaultProvider } }, this.markusConfigPath);
82467
+ saveConfig({ llm: configUpdates }, this.markusConfigPath);
81439
82468
  } catch (e) {
81440
- 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) });
81441
82470
  }
81442
82471
  this.auditService?.record({
81443
82472
  orgId: "system",
81444
82473
  type: "settings_changed",
81445
- action: "llm_default_provider",
81446
- detail: `defaultProvider=${defaultProvider}`,
82474
+ action: "llm_settings",
82475
+ detail: `${defaultProvider ? `defaultProvider=${defaultProvider}` : ""}${autoFallback !== void 0 ? ` autoFallback=${autoFallback}` : ""}`.trim(),
81447
82476
  userId: auth.userId,
81448
82477
  success: true
81449
82478
  });
@@ -81567,6 +82596,70 @@ EXPLANATION_END`;
81567
82596
  });
81568
82597
  return;
81569
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
+ }
81570
82663
  if (path === "/api/settings/llm/models" && req.method === "GET") {
81571
82664
  if (!this.llmRouter) {
81572
82665
  this.json(res, 200, { models: [] });
@@ -82833,6 +83926,8 @@ EXPLANATION_END`;
82833
83926
  const home = homedir22();
82834
83927
  const results = {};
82835
83928
  const mdExts = [".md", ".markdown"];
83929
+ const htmlExts = [".html", ".htm"];
83930
+ const jsonExts = [".json"];
82836
83931
  const imageExts = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
82837
83932
  for (const p of paths.slice(0, 50)) {
82838
83933
  try {
@@ -82848,6 +83943,10 @@ EXPLANATION_END`;
82848
83943
  let type = "text";
82849
83944
  if (mdExts.includes(ext))
82850
83945
  type = "markdown";
83946
+ else if (htmlExts.includes(ext))
83947
+ type = "html";
83948
+ else if (jsonExts.includes(ext))
83949
+ type = "json";
82851
83950
  else if (imageExts.includes(ext))
82852
83951
  type = "image";
82853
83952
  else if (!isFile)
@@ -82881,6 +83980,27 @@ EXPLANATION_END`;
82881
83980
  return;
82882
83981
  }
82883
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
+ }
82884
84004
  if (!stat.isFile()) {
82885
84005
  this.json(res, 400, { error: "Path is not a file" });
82886
84006
  return;
@@ -82904,8 +84024,20 @@ EXPLANATION_END`;
82904
84024
  } else {
82905
84025
  const content = readFileSync26(resolved, "utf-8");
82906
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";
82907
84039
  this.json(res, 200, {
82908
- type: mdExts.includes(ext) ? "markdown" : "text",
84040
+ type: fileType,
82909
84041
  name: resolved.split("/").pop(),
82910
84042
  content
82911
84043
  });
@@ -83845,6 +84977,7 @@ EXPLANATION_END`;
83845
84977
  exact("/api/settings/llm/models", "GET"),
83846
84978
  exact("/api/settings/agent", "GET", "POST"),
83847
84979
  exact("/api/settings/browser", "GET", "POST"),
84980
+ exact("/api/settings/search", "GET", "POST"),
83848
84981
  exact("/api/settings/env-models", "GET", "POST"),
83849
84982
  exact("/api/settings/detect-ollama", "GET"),
83850
84983
  exact("/api/settings/export", "POST"),
@@ -84068,6 +85201,7 @@ EXPLANATION_END`;
84068
85201
  statusCounts: taskDashboard.statusCounts,
84069
85202
  successRate: taskSuccessRate,
84070
85203
  blockedCount: blockedTasks,
85204
+ stuckBlockedCount: taskDashboard.stuckBlockedCount,
84071
85205
  averageCompletionTimeMs: taskDashboard.averageCompletionTimeMs,
84072
85206
  recentActivity: taskDashboard.recentActivity.slice(0, 10)
84073
85207
  },
@@ -84680,7 +85814,7 @@ var init_hitl_service = __esm({
84680
85814
  });
84681
85815
 
84682
85816
  // ../org-manager/dist/billing-service.js
84683
- import { randomBytes as randomBytes4 } from "node:crypto";
85817
+ import { randomBytes as randomBytes5 } from "node:crypto";
84684
85818
  var log58, DEFAULT_PLANS, keyCounter, BillingService;
84685
85819
  var init_billing_service = __esm({
84686
85820
  "../org-manager/dist/billing-service.js"() {
@@ -84814,7 +85948,7 @@ var init_billing_service = __esm({
84814
85948
  }
84815
85949
  createAPIKey(orgId2, name, scopes = ["*"], expiresInDays) {
84816
85950
  const id = `mk_${(++keyCounter).toString(36)}_${Date.now().toString(36)}`;
84817
- const key2 = `mk_${randomBytes4(32).toString("hex")}`;
85951
+ const key2 = `mk_${randomBytes5(32).toString("hex")}`;
84818
85952
  const now3 = /* @__PURE__ */ new Date();
84819
85953
  const apiKey = {
84820
85954
  id,
@@ -86166,6 +87300,8 @@ var init_deliverable_service = __esm({
86166
87300
  summary: opts.summary,
86167
87301
  tags: opts.tags ?? existing.tags
86168
87302
  };
87303
+ if (opts.format !== void 0)
87304
+ patch.format = opts.format;
86169
87305
  if (opts.artifactType !== void 0)
86170
87306
  patch.artifactType = opts.artifactType;
86171
87307
  if (opts.artifactData !== void 0)
@@ -86194,6 +87330,7 @@ var init_deliverable_service = __esm({
86194
87330
  title: opts.title,
86195
87331
  summary: opts.summary,
86196
87332
  reference: ref ?? "",
87333
+ format: opts.format,
86197
87334
  tags: opts.tags ?? [],
86198
87335
  status: "active",
86199
87336
  taskId: opts.taskId,
@@ -86214,6 +87351,7 @@ var init_deliverable_service = __esm({
86214
87351
  title: opts.title,
86215
87352
  summary: opts.summary,
86216
87353
  reference: ref ?? "",
87354
+ format: opts.format,
86217
87355
  tags: opts.tags ?? [],
86218
87356
  status: "active",
86219
87357
  taskId: opts.taskId,
@@ -86301,6 +87439,8 @@ var init_deliverable_service = __esm({
86301
87439
  d.summary = data.summary;
86302
87440
  if (data.reference !== void 0)
86303
87441
  d.reference = data.reference;
87442
+ if (data.format !== void 0)
87443
+ d.format = data.format;
86304
87444
  if (data.tags !== void 0)
86305
87445
  d.tags = data.tags;
86306
87446
  if (data.status !== void 0)
@@ -86480,6 +87620,7 @@ var init_deliverable_service = __esm({
86480
87620
  title: r.title,
86481
87621
  summary: r.summary,
86482
87622
  reference: r.reference,
87623
+ format: r.format ?? void 0,
86483
87624
  tags: this.parseTags(r.tags),
86484
87625
  status: r.status,
86485
87626
  taskId: r.taskId ?? void 0,
@@ -86736,12 +87877,16 @@ var init_report_service = __esm({
86736
87877
  }
86737
87878
  buildCostSummary(scopeId, start, end) {
86738
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;
86739
87884
  return {
86740
87885
  totalTokens: usage.llmTokens,
86741
- totalEstimatedCost: usage.llmTokens * 3e-6,
87886
+ totalEstimatedCost: cost,
86742
87887
  byAgent: [],
86743
87888
  byCategory: [
86744
- { category: "llm_tokens", tokens: usage.llmTokens, cost: usage.llmTokens * 3e-6 },
87889
+ { category: "llm_tokens", tokens: usage.llmTokens, cost },
86745
87890
  { category: "tool_calls", tokens: 0, cost: 0 }
86746
87891
  ],
86747
87892
  trend: "stable"
@@ -87065,17 +88210,21 @@ var init_stale_detector = __esm({
87065
88210
  });
87066
88211
 
87067
88212
  // ../org-manager/dist/scheduled-task-runner.js
87068
- var log69, ScheduledTaskRunner;
88213
+ var log69, MIN_STAGGER_MS, MAX_STAGGER_MS, ScheduledTaskRunner;
87069
88214
  var init_scheduled_task_runner = __esm({
87070
88215
  "../org-manager/dist/scheduled-task-runner.js"() {
87071
88216
  "use strict";
87072
88217
  init_dist();
87073
88218
  log69 = createLogger("scheduled-task-runner");
88219
+ MIN_STAGGER_MS = 2 * 6e4;
88220
+ MAX_STAGGER_MS = 15 * 6e4;
87074
88221
  ScheduledTaskRunner = class {
87075
88222
  taskService;
87076
88223
  pollIntervalMs;
87077
88224
  timer;
88225
+ staggerTimers = [];
87078
88226
  running = false;
88227
+ startedAt = 0;
87079
88228
  constructor(taskService, pollIntervalMs = 6e4) {
87080
88229
  this.taskService = taskService;
87081
88230
  this.pollIntervalMs = pollIntervalMs;
@@ -87084,6 +88233,8 @@ var init_scheduled_task_runner = __esm({
87084
88233
  if (this.running)
87085
88234
  return;
87086
88235
  this.running = true;
88236
+ this.startedAt = Date.now();
88237
+ this.tick().catch((e) => log69.error("Initial scheduled task tick failed", { error: String(e) }));
87087
88238
  this.timer = setInterval(() => {
87088
88239
  this.tick().catch((e) => log69.error("Scheduled task tick failed", { error: String(e) }));
87089
88240
  }, this.pollIntervalMs);
@@ -87094,15 +88245,26 @@ var init_scheduled_task_runner = __esm({
87094
88245
  clearInterval(this.timer);
87095
88246
  this.timer = void 0;
87096
88247
  }
88248
+ for (const t of this.staggerTimers)
88249
+ clearTimeout(t);
88250
+ this.staggerTimers = [];
87097
88251
  this.running = false;
87098
88252
  log69.info("ScheduledTaskRunner stopped");
87099
88253
  }
87100
88254
  isRunning() {
87101
88255
  return this.running;
87102
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
+ }
87103
88264
  async tick() {
87104
88265
  const scheduledTasks = this.taskService.listScheduledTasks();
87105
88266
  const now3 = Date.now();
88267
+ const startup = this.isStartupPhase();
87106
88268
  for (const task of scheduledTasks) {
87107
88269
  if (!task.scheduleConfig)
87108
88270
  continue;
@@ -87119,6 +88281,22 @@ var init_scheduled_task_runner = __esm({
87119
88281
  if (config.paused) {
87120
88282
  continue;
87121
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
+ }
87122
88300
  try {
87123
88301
  await this.fireScheduledTask(task);
87124
88302
  } catch (e) {
@@ -87218,7 +88396,8 @@ function openSqlite(dbPath) {
87218
88396
  { table: "projects", column: "created_by", sql: "ALTER TABLE projects ADD COLUMN created_by TEXT" },
87219
88397
  { table: "approvals", column: "target_user_id", sql: "ALTER TABLE approvals ADD COLUMN target_user_id TEXT" },
87220
88398
  { table: "users", column: "deleted_at", sql: "ALTER TABLE users ADD COLUMN deleted_at TEXT" },
87221
- { 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" }
87222
88401
  ];
87223
88402
  for (const m of migrations) {
87224
88403
  const cols = _db.prepare(`PRAGMA table_info(${m.table})`).all();
@@ -87523,6 +88702,7 @@ CREATE TABLE IF NOT EXISTS deliverables (
87523
88702
  title TEXT NOT NULL,
87524
88703
  summary TEXT NOT NULL DEFAULT '',
87525
88704
  reference TEXT NOT NULL DEFAULT '',
88705
+ format TEXT,
87526
88706
  tags TEXT NOT NULL DEFAULT '[]',
87527
88707
  status TEXT NOT NULL DEFAULT 'active',
87528
88708
  task_id TEXT,
@@ -89500,11 +90680,11 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89500
90680
  async create(data) {
89501
90681
  const n2 = now2();
89502
90682
  this.db.prepare(`
89503
- INSERT INTO deliverables (id, type, title, summary, reference, tags, status,
90683
+ INSERT INTO deliverables (id, type, title, summary, reference, format, tags, status,
89504
90684
  task_id, agent_id, project_id, requirement_id, diff_stats, test_results,
89505
90685
  artifact_type, artifact_data, access_count, created_at, updated_at)
89506
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
89507
- `).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);
89508
90688
  return this.findById(data.id);
89509
90689
  }
89510
90690
  async findById(id) {
@@ -89567,6 +90747,10 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89567
90747
  sets.push("reference = ?");
89568
90748
  vals.push(patch.reference);
89569
90749
  }
90750
+ if (patch.format !== void 0) {
90751
+ sets.push("format = ?");
90752
+ vals.push(patch.format);
90753
+ }
89570
90754
  if (patch.type !== void 0) {
89571
90755
  sets.push("type = ?");
89572
90756
  vals.push(patch.type);
@@ -89615,6 +90799,7 @@ CREATE INDEX IF NOT EXISTS idx_st_entity ON status_transitions(entity_type, enti
89615
90799
  title: r["title"],
89616
90800
  summary: r["summary"],
89617
90801
  reference: r["reference"],
90802
+ format: r["format"] ?? null,
89618
90803
  tags: fromJson(r["tags"]) ?? [],
89619
90804
  status: r["status"],
89620
90805
  taskId: r["task_id"],
@@ -91154,6 +92339,7 @@ var init_logger2 = __esm({
91154
92339
 
91155
92340
  // src/utils/browser.ts
91156
92341
  import { exec } from "node:child_process";
92342
+ import { get as httpGet } from "node:http";
91157
92343
  import { platform as platform2 } from "node:os";
91158
92344
  function openBrowser(url) {
91159
92345
  if (process.env["NO_BROWSER"]) return;
@@ -91164,6 +92350,27 @@ function openBrowser(url) {
91164
92350
  }
91165
92351
  });
91166
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
+ }
91167
92374
  var init_browser = __esm({
91168
92375
  "src/utils/browser.ts"() {
91169
92376
  "use strict";
@@ -92106,6 +93313,9 @@ async function createServices(config) {
92106
93313
  llmRouter.setProviderEnabled(name, false);
92107
93314
  }
92108
93315
  }
93316
+ if (config.llm.autoFallback === false) {
93317
+ llmRouter.setAutoFallback(false);
93318
+ }
92109
93319
  if (config.llm.customModels) {
92110
93320
  for (const [providerName, models] of Object.entries(config.llm.customModels)) {
92111
93321
  for (const model of models) {
@@ -92280,6 +93490,9 @@ async function startServer(config, values) {
92280
93490
  if (config.integrations?.search?.braveApiKey && !process.env["BRAVE_SEARCH_API_KEY"]) {
92281
93491
  process.env["BRAVE_SEARCH_API_KEY"] = config.integrations.search.braveApiKey;
92282
93492
  }
93493
+ if (config.integrations?.search?.bochaApiKey && !process.env["BOCHA_API_KEY"]) {
93494
+ process.env["BOCHA_API_KEY"] = config.integrations.search.bochaApiKey;
93495
+ }
92283
93496
  const apiPort = Number(values["port"]) || config.server.apiPort;
92284
93497
  const {
92285
93498
  orgService,
@@ -92529,20 +93742,37 @@ async function startServer(config, values) {
92529
93742
  const sessionUserId = targetUserId || defaultSessionUserId;
92530
93743
  const mainSession = storage.chatSessionRepo.getOrCreateMainSession(agentId2, sessionUserId);
92531
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(", ")} -->` : "";
92532
93752
  const formattedMsg = `**${title}**
92533
93753
 
92534
- ${body}`;
93754
+ ${body}${contextSuffix}`;
93755
+ const msgMetadata = {
93756
+ notifyUser: true,
93757
+ priority: priority ?? "normal",
93758
+ ...taskId2 ? { taskId: taskId2 } : {},
93759
+ ...requirementId2 ? { requirementId: requirementId2 } : {}
93760
+ };
92535
93761
  const msg = storage.chatSessionRepo.appendMessage(
92536
93762
  mainSession.id,
92537
93763
  agentId2,
92538
93764
  "assistant",
92539
93765
  formattedMsg,
92540
93766
  0,
92541
- {}
93767
+ msgMetadata
92542
93768
  );
92543
93769
  storage.chatSessionRepo.updateLastMessage(mainSession.id);
92544
93770
  ws.broadcastProactiveMessage(agentId2, agent.config.name, mainSession.id, msg.id, formattedMsg, {
92545
- isMainSession: true
93771
+ isMainSession: true,
93772
+ notifyUser: true,
93773
+ priority: priority ?? "normal",
93774
+ taskId: taskId2,
93775
+ requirementId: requirementId2
92546
93776
  }, sessionUserId);
92547
93777
  const hasTask = !!taskId2;
92548
93778
  hitlService.notify({
@@ -93186,13 +94416,9 @@ ${reason}`;
93186
94416
  const logFile = getStartupLogFile();
93187
94417
  const logFileName = logFile.split("/").pop() ?? logFile;
93188
94418
  const uiUrl = `http://localhost:${apiPort}`;
94419
+ progress.finish(uiUrl);
93189
94420
  if (!process.env["NO_BROWSER"] && webUiDir) {
93190
- progress.finish(uiUrl);
93191
- setTimeout(() => {
93192
- openBrowser(uiUrl);
93193
- }, 500);
93194
- } else {
93195
- progress.finish(uiUrl);
94421
+ openBrowserAfterHealthCheck(uiUrl, `${uiUrl}/api/health`);
93196
94422
  }
93197
94423
  checkForUpdate().then((info4) => {
93198
94424
  if (info4.updateAvailable) {
@@ -96301,7 +97527,7 @@ var connect_exports = {};
96301
97527
  __export(connect_exports, {
96302
97528
  registerConnectCommands: () => registerConnectCommands
96303
97529
  });
96304
- import { randomBytes as randomBytes5 } from "node:crypto";
97530
+ import { randomBytes as randomBytes6 } from "node:crypto";
96305
97531
  function registerConnectCommands(program2) {
96306
97532
  const root = program2.command("connect").description("Connect / disconnect external agent platforms");
96307
97533
  root.command("scan").description("Scan for installed agent platforms that can be connected").action(async (_opts, cmd) => {
@@ -96360,7 +97586,7 @@ function registerConnectCommands(program2) {
96360
97586
  fail(`Unknown platform "${platform3}". Run \`markus connect list\` to see available connectors.`);
96361
97587
  return;
96362
97588
  }
96363
- const agentId2 = opts.agentId || `${platform3}-${randomBytes5(4).toString("hex")}`;
97589
+ const agentId2 = opts.agentId || `${platform3}-${randomBytes6(4).toString("hex")}`;
96364
97590
  const agentName = opts.agentName || connector.defaultAgentName || `${connector.displayName} Agent`;
96365
97591
  const capabilities = opts.capabilities ? String(opts.capabilities).split(",").map((s2) => s2.trim()).filter(Boolean) : connector.defaultCapabilities ?? [];
96366
97592
  const client = createClient(g);
@@ -96534,7 +97760,7 @@ __export(install_agent_exports, {
96534
97760
  registerInstallAgentCommands: () => registerInstallAgentCommands
96535
97761
  });
96536
97762
  import { execSync as execSync5 } from "node:child_process";
96537
- import { randomBytes as randomBytes6 } from "node:crypto";
97763
+ import { randomBytes as randomBytes7 } from "node:crypto";
96538
97764
  function registerInstallAgentCommands(program2) {
96539
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) => {
96540
97766
  const g = cmd.optsWithGlobals();
@@ -96590,7 +97816,7 @@ function registerInstallAgentCommands(program2) {
96590
97816
  }
96591
97817
  const client = createClient(g);
96592
97818
  const serverUrl = g.server || process.env["MARKUS_API_URL"] || "http://localhost:8056";
96593
- const agentId2 = `${platform3}-${randomBytes6(4).toString("hex")}`;
97819
+ const agentId2 = `${platform3}-${randomBytes7(4).toString("hex")}`;
96594
97820
  const agentName = opts.agentName || connector.defaultAgentName || `${connector.displayName} Agent`;
96595
97821
  const capabilities = connector.defaultCapabilities ?? [];
96596
97822
  try {