@markus-global/cli 0.4.22 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +8 -0
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/commands/start.d.ts.map +1 -1
  5. package/dist/commands/start.js +16 -0
  6. package/dist/commands/start.js.map +1 -1
  7. package/dist/markus.mjs +939 -241
  8. package/dist/web-ui/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  9. package/dist/web-ui/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  10. package/dist/web-ui/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  11. package/dist/web-ui/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  12. package/dist/web-ui/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  13. package/dist/web-ui/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  14. package/dist/web-ui/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  15. package/dist/web-ui/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  16. package/dist/web-ui/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  17. package/dist/web-ui/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  18. package/dist/web-ui/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  19. package/dist/web-ui/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  20. package/dist/web-ui/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  21. package/dist/web-ui/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  22. package/dist/web-ui/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  23. package/dist/web-ui/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  24. package/dist/web-ui/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  25. package/dist/web-ui/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  26. package/dist/web-ui/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  27. package/dist/web-ui/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  28. package/dist/web-ui/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  29. package/dist/web-ui/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  30. package/dist/web-ui/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  31. package/dist/web-ui/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  32. package/dist/web-ui/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  33. package/dist/web-ui/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  34. package/dist/web-ui/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  35. package/dist/web-ui/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  36. package/dist/web-ui/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  37. package/dist/web-ui/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  38. package/dist/web-ui/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  39. package/dist/web-ui/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  40. package/dist/web-ui/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  41. package/dist/web-ui/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  42. package/dist/web-ui/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  43. package/dist/web-ui/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  44. package/dist/web-ui/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  45. package/dist/web-ui/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  46. package/dist/web-ui/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  47. package/dist/web-ui/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  48. package/dist/web-ui/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  49. package/dist/web-ui/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  50. package/dist/web-ui/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  51. package/dist/web-ui/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  52. package/dist/web-ui/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  53. package/dist/web-ui/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  54. package/dist/web-ui/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  55. package/dist/web-ui/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  56. package/dist/web-ui/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  57. package/dist/web-ui/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  58. package/dist/web-ui/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  59. package/dist/web-ui/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  60. package/dist/web-ui/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  61. package/dist/web-ui/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  62. package/dist/web-ui/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  63. package/dist/web-ui/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  64. package/dist/web-ui/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  65. package/dist/web-ui/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  66. package/dist/web-ui/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  67. package/dist/web-ui/assets/index-FxtMolbG.js +343 -0
  68. package/dist/web-ui/assets/index-jBDtl4o2.css +1 -0
  69. package/dist/web-ui/index.html +2 -2
  70. package/package.json +1 -1
  71. package/templates/roles/org-manager/HEARTBEAT.md +8 -0
  72. package/templates/roles/secretary/HEARTBEAT.md +8 -0
  73. package/templates/skills/agent-building/SKILL.md +14 -5
  74. package/templates/skills/team-building/SKILL.md +22 -5
  75. package/dist/web-ui/assets/index-DEkXd9d7.css +0 -1
  76. package/dist/web-ui/assets/index-HAWGXtTM.js +0 -66
package/dist/markus.mjs CHANGED
@@ -41155,13 +41155,41 @@ var init_web_fetch = __esm({
41155
41155
  });
41156
41156
 
41157
41157
  // ../core/dist/tools/web-search.js
41158
+ async function resolveProxyDispatcher() {
41159
+ if (_dispatcher !== false)
41160
+ return _dispatcher || void 0;
41161
+ const proxyUrl = process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || process.env["https_proxy"] || process.env["http_proxy"];
41162
+ if (!proxyUrl) {
41163
+ _dispatcher = void 0;
41164
+ return void 0;
41165
+ }
41166
+ try {
41167
+ const load = new Function("id", "return import(id)");
41168
+ const { ProxyAgent } = await load("undici");
41169
+ _dispatcher = new ProxyAgent(proxyUrl);
41170
+ } catch {
41171
+ _dispatcher = void 0;
41172
+ }
41173
+ return _dispatcher || void 0;
41174
+ }
41175
+ async function proxyFetch(url, init) {
41176
+ const dispatcher = await resolveProxyDispatcher();
41177
+ const signal = init?.signal ?? AbortSignal.timeout(SEARCH_TIMEOUT_MS);
41178
+ const opts = { ...init, signal };
41179
+ if (dispatcher)
41180
+ opts["dispatcher"] = dispatcher;
41181
+ return fetch(url, opts);
41182
+ }
41183
+ function hasProxy() {
41184
+ return !!(process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || process.env["https_proxy"] || process.env["http_proxy"]);
41185
+ }
41158
41186
  async function searchSerper(query2, maxResults) {
41159
41187
  const apiKey = process.env["SERPER_API_KEY"];
41160
41188
  if (!apiKey)
41161
41189
  throw new Error("SERPER_API_KEY not configured");
41162
41190
  let res;
41163
41191
  try {
41164
- res = await fetch("https://google.serper.dev/search", {
41192
+ res = await proxyFetch("https://google.serper.dev/search", {
41165
41193
  method: "POST",
41166
41194
  headers: {
41167
41195
  "X-API-KEY": apiKey,
@@ -41189,7 +41217,7 @@ async function searchBrave(query2, maxResults) {
41189
41217
  const params = new URLSearchParams({ q: query2, count: String(maxResults) });
41190
41218
  let res;
41191
41219
  try {
41192
- res = await fetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
41220
+ res = await proxyFetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
41193
41221
  headers: {
41194
41222
  "Accept": "application/json",
41195
41223
  "Accept-Encoding": "gzip",
@@ -41209,23 +41237,28 @@ async function searchBrave(query2, maxResults) {
41209
41237
  date: r.page_age
41210
41238
  }));
41211
41239
  }
41212
- async function searchDuckDuckGoLite(query2, maxResults) {
41240
+ async function searchDuckDuckGo(query2, maxResults) {
41213
41241
  const encoded = encodeURIComponent(query2);
41214
- let res;
41215
- try {
41216
- res = await fetch(`https://lite.duckduckgo.com/lite/?q=${encoded}`, {
41217
- headers: {
41218
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
41242
+ let lastError;
41243
+ for (const base of DDG_ENDPOINTS) {
41244
+ try {
41245
+ const res = await proxyFetch(`${base}?q=${encoded}`, {
41246
+ headers: { "User-Agent": DDG_UA }
41247
+ });
41248
+ if (!res.ok) {
41249
+ lastError = new Error(`HTTP ${res.status} ${res.statusText}`);
41250
+ continue;
41219
41251
  }
41220
- });
41221
- } catch (err) {
41222
- throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
41223
- }
41224
- if (!res.ok) {
41225
- return searchDuckDuckGoHtml(query2, maxResults);
41252
+ const html = await res.text();
41253
+ const results = base.includes("lite") ? parseDDGLite(html, maxResults) : parseDDGHtml(html, maxResults);
41254
+ if (results.length > 0)
41255
+ return results;
41256
+ lastError = new Error(`Parsed 0 results from ${base}`);
41257
+ } catch (err) {
41258
+ lastError = err instanceof Error ? err : new Error(String(err));
41259
+ }
41226
41260
  }
41227
- const html = await res.text();
41228
- return parseDDGLite(html, maxResults);
41261
+ throw new Error(`Network error: ${lastError?.message ?? "all DuckDuckGo endpoints failed"}`);
41229
41262
  }
41230
41263
  function parseDDGLite(html, max) {
41231
41264
  const results = [];
@@ -41264,21 +41297,7 @@ function parseDDGLite(html, max) {
41264
41297
  }
41265
41298
  return results;
41266
41299
  }
41267
- async function searchDuckDuckGoHtml(query2, maxResults) {
41268
- const encoded = encodeURIComponent(query2);
41269
- let res;
41270
- try {
41271
- res = await fetch(`https://html.duckduckgo.com/html/?q=${encoded}`, {
41272
- headers: {
41273
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
41274
- }
41275
- });
41276
- } catch (err) {
41277
- throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
41278
- }
41279
- if (!res.ok)
41280
- throw new Error(`HTTP ${res.status} ${res.statusText}`);
41281
- const html = await res.text();
41300
+ function parseDDGHtml(html, maxResults) {
41282
41301
  const results = [];
41283
41302
  const linkRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
41284
41303
  const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
@@ -41306,10 +41325,12 @@ async function searchDuckDuckGoHtml(query2, maxResults) {
41306
41325
  function stripHtml(html) {
41307
41326
  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();
41308
41327
  }
41309
- var WebSearchTool;
41328
+ var SEARCH_TIMEOUT_MS, _dispatcher, WebSearchTool, DDG_UA, DDG_ENDPOINTS;
41310
41329
  var init_web_search = __esm({
41311
41330
  "../core/dist/tools/web-search.js"() {
41312
41331
  "use strict";
41332
+ SEARCH_TIMEOUT_MS = 15e3;
41333
+ _dispatcher = false;
41313
41334
  WebSearchTool = {
41314
41335
  name: "web_search",
41315
41336
  description: "Search the web for real-time information. Returns search result titles, snippets, and URLs. Use this when you need up-to-date information that might not be in your training data.",
@@ -41333,7 +41354,7 @@ var init_web_search = __esm({
41333
41354
  const backends = [
41334
41355
  { name: "Serper", fn: searchSerper },
41335
41356
  { name: "Brave", fn: searchBrave },
41336
- { name: "DuckDuckGo", fn: searchDuckDuckGoLite }
41357
+ { name: "DuckDuckGo", fn: searchDuckDuckGo }
41337
41358
  ];
41338
41359
  const errors = [];
41339
41360
  for (const { name, fn } of backends) {
@@ -41348,13 +41369,27 @@ var init_web_search = __esm({
41348
41369
  errors.push({ backend: name, error: message });
41349
41370
  }
41350
41371
  }
41372
+ const hasNetworkErr = errors.some((e) => e.error.includes("Network error") || e.error.includes("timed out"));
41373
+ const hints = [];
41374
+ if (hasNetworkErr && !hasProxy()) {
41375
+ hints.push("All network requests failed and no HTTP proxy is configured. If you are behind a firewall or in a restricted network, set HTTPS_PROXY (e.g. export HTTPS_PROXY=http://127.0.0.1:7890).");
41376
+ }
41377
+ if (hasNetworkErr && hasProxy()) {
41378
+ hints.push(`HTTP proxy is configured (${process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || process.env["https_proxy"] || process.env["http_proxy"]}), but network requests still failed. Verify the proxy is reachable and allows outbound HTTPS.`);
41379
+ }
41351
41380
  return JSON.stringify({
41352
41381
  status: "error",
41353
41382
  error: "All search backends failed.",
41354
- details: errors
41383
+ details: errors,
41384
+ ...hints.length > 0 ? { hints } : {}
41355
41385
  });
41356
41386
  }
41357
41387
  };
41388
+ 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";
41389
+ DDG_ENDPOINTS = [
41390
+ "https://lite.duckduckgo.com/lite/",
41391
+ "https://html.duckduckgo.com/html/"
41392
+ ];
41358
41393
  }
41359
41394
  });
41360
41395
 
@@ -42917,6 +42952,18 @@ ${typeLabel}${item.payload.content}`;
42917
42952
  this.persistence?.updateStatus(item.id, "deferred", { deferredUntil: until });
42918
42953
  return item;
42919
42954
  }
42955
+ /**
42956
+ * Defer an item that has already been dequeued (not in the queue).
42957
+ * Used when processing is interrupted (preempted) and the item should
42958
+ * be resumed later. Only updates persistence — does NOT re-insert
42959
+ * into the in-memory queue (resurfaceDue handles that on the next idle cycle).
42960
+ */
42961
+ deferDequeued(item, until) {
42962
+ item.status = "deferred";
42963
+ item.deferredUntil = until;
42964
+ item.startedAt = void 0;
42965
+ this.persistence?.updateStatus(item.id, "deferred", { deferredUntil: until });
42966
+ }
42920
42967
  /**
42921
42968
  * Mark an item as merged into another item.
42922
42969
  */
@@ -43158,7 +43205,7 @@ function detectAbnormalCompletion(reply, item) {
43158
43205
  const registry = MAILBOX_TYPE_REGISTRY[item.sourceType];
43159
43206
  if (!registry?.invokesLLM)
43160
43207
  return void 0;
43161
- if (reply === "[preempted]")
43208
+ if (reply === "[preempted]" || reply === "[cancelled]")
43162
43209
  return void 0;
43163
43210
  const isUserFacing = item.sourceType === "human_chat" || item.sourceType === "a2a_message";
43164
43211
  if (isUserFacing)
@@ -43195,6 +43242,8 @@ var init_attention = __esm({
43195
43242
  triageToolHandlers;
43196
43243
  triageChatFn;
43197
43244
  lastTriageResult;
43245
+ /** Tracks whether the last yield-point decision was 'cancel' vs 'preempt'. */
43246
+ lastYieldDecision;
43198
43247
  unsubscribeNewItem;
43199
43248
  decisions = [];
43200
43249
  watchdogTimer;
@@ -43391,6 +43440,7 @@ var init_attention = __esm({
43391
43440
  this.interruptSignal = false;
43392
43441
  this.pendingInterruptItem = void 0;
43393
43442
  this.criticalInterruptResolve = void 0;
43443
+ this.lastYieldDecision = void 0;
43394
43444
  this.processingStartedAt = Date.now();
43395
43445
  this.delegate?.onFocusChanged(item);
43396
43446
  let reply = void 0;
@@ -43425,6 +43475,27 @@ var init_attention = __esm({
43425
43475
  this.processingStartedAt = void 0;
43426
43476
  if (timedOut) {
43427
43477
  this.mailbox.requeue(item);
43478
+ } else if (reply === "[cancelled]" || this.lastYieldDecision === "cancel") {
43479
+ log14.info("Item cancelled \u2014 dropping permanently", {
43480
+ agentId: this.agentId,
43481
+ itemId: item.id,
43482
+ type: item.sourceType
43483
+ });
43484
+ this.mailbox.complete(item.id);
43485
+ } else if (reply === "[preempted]" || this.lastYieldDecision === "preempt") {
43486
+ log14.info("Item preempted (paused) \u2014 deferring for later resumption", {
43487
+ agentId: this.agentId,
43488
+ itemId: item.id,
43489
+ type: item.sourceType
43490
+ });
43491
+ this.mailbox.deferDequeued(item);
43492
+ } else if (!this.running) {
43493
+ log14.info("Agent stopped during processing \u2014 requeueing item", {
43494
+ agentId: this.agentId,
43495
+ itemId: item.id,
43496
+ type: item.sourceType
43497
+ });
43498
+ this.mailbox.requeue(item);
43428
43499
  } else {
43429
43500
  const abnormalReason = detectAbnormalCompletion(reply, item);
43430
43501
  const retries = item.retryCount ?? 0;
@@ -43500,7 +43571,8 @@ var init_attention = __esm({
43500
43571
  *
43501
43572
  * Returns the decision type so the caller knows what to do:
43502
43573
  * - 'continue' → keep working on current task
43503
- * - 'preempt' → caller should save state and return; controller handles the switch
43574
+ * - 'preempt' → caller should save state and return '[preempted]'; item is deferred for later resumption
43575
+ * - 'cancel' → caller should abort and return '[cancelled]'; item is permanently dropped
43504
43576
  * - 'merge' → item was absorbed into current work (caller may inject into session)
43505
43577
  */
43506
43578
  async checkYieldPoint() {
@@ -43533,8 +43605,13 @@ var init_attention = __esm({
43533
43605
  return { decision: "defer", item: newItem, reasoning };
43534
43606
  }
43535
43607
  if (decisionType === "preempt") {
43608
+ this.lastYieldDecision = "preempt";
43536
43609
  return { decision: "preempt", item: newItem, reasoning };
43537
43610
  }
43611
+ if (decisionType === "cancel") {
43612
+ this.lastYieldDecision = "cancel";
43613
+ return { decision: "cancel", item: newItem, reasoning };
43614
+ }
43538
43615
  return { decision: "continue", reasoning };
43539
43616
  }
43540
43617
  /**
@@ -43617,7 +43694,7 @@ var init_attention = __esm({
43617
43694
  try {
43618
43695
  const prompt = this.buildJudgePrompt(currentItem, newItem);
43619
43696
  const llmDecision = await this.llmJudge(prompt);
43620
- const validDecisions = ["continue", "preempt", "merge", "defer"];
43697
+ const validDecisions = ["continue", "preempt", "cancel", "merge", "defer"];
43621
43698
  if (validDecisions.includes(llmDecision))
43622
43699
  return llmDecision;
43623
43700
  } catch (err) {
@@ -43632,17 +43709,29 @@ var init_attention = __esm({
43632
43709
  const recentDecs = this.decisions.slice(-5).map((d) => `- ${d.decisionType}: ${d.reasoning}`).join("\n");
43633
43710
  const queuedItems = this.mailbox.getQueuedItems();
43634
43711
  const queueSection = queuedItems.length > 0 ? queuedItems.map((i) => ` - [${i.sourceType}] p${i.priority}: "${i.payload.summary}"`).join("\n") : " (empty)";
43712
+ const currentContent = currentItem.payload.content?.slice(0, 500) ?? "";
43713
+ const newContent = newItem.payload.content?.slice(0, 800) ?? "";
43635
43714
  return [
43636
- "You are deciding how to handle a new incoming item in your mailbox.",
43637
- "CRITICAL RULE: Human messages (human_chat) and human comments ALWAYS take priority over all other work.",
43715
+ "You are deciding whether to INTERRUPT your current work to handle a new incoming message.",
43716
+ "",
43717
+ "RULES (in priority order):",
43718
+ "1. Human messages (human_chat) ALWAYS preempt non-human work \u2014 users are top priority.",
43719
+ '2. If the new message explicitly CANCELS or REVOKES the current work (e.g. "cancel that task",',
43720
+ ` "don't publish this anymore", "delete the draft"), choose cancel \u2014 current work is permanently abandoned.`,
43721
+ '3. If the new message asks to PAUSE or DELAY current work (e.g. "hold off for now",',
43722
+ ' "wait before deploying", "pause that"), choose preempt \u2014 current work is saved and can be resumed later.',
43723
+ "4. If the new message is about the SAME task/topic as current work and adds info, consider merge.",
43724
+ "5. If the new message is lower priority and unrelated, continue current work.",
43638
43725
  "",
43639
43726
  `CURRENT FOCUS: [${currentItem.sourceType}] priority=${currentItem.priority}`,
43640
- ` "${currentItem.payload.summary}"`,
43727
+ ` Summary: "${currentItem.payload.summary}"`,
43641
43728
  ` Task: ${currentItem.payload.taskId ?? "none"}`,
43729
+ currentContent ? ` Content preview: ${currentContent}` : "",
43642
43730
  "",
43643
43731
  `NEW ITEM: [${newItem.sourceType}] priority=${newItem.priority}`,
43644
- ` "${newItem.payload.summary}"`,
43732
+ ` Summary: "${newItem.payload.summary}"`,
43645
43733
  ` Task: ${newItem.payload.taskId ?? "none"}`,
43734
+ newContent ? ` Content: ${newContent}` : "",
43646
43735
  "",
43647
43736
  `FULL QUEUE (${this.mailbox.depth} items):`,
43648
43737
  queueSection,
@@ -43650,7 +43739,14 @@ var init_attention = __esm({
43650
43739
  "RECENT DECISIONS:",
43651
43740
  recentDecs || "(none)",
43652
43741
  "",
43653
- "Choose exactly one: continue | preempt | merge | defer",
43742
+ "DECISIONS:",
43743
+ "- preempt: PAUSE current work (session saved for later resumption) and handle the new item",
43744
+ "- cancel: PERMANENTLY STOP current work (session dropped, will NOT be resumed) and handle the new item",
43745
+ "- merge: Inject new item into current session as additional context (no interruption)",
43746
+ "- defer: Put the new item aside for later (no interruption)",
43747
+ "- continue: Ignore the new item for now, keep working",
43748
+ "",
43749
+ "Choose exactly one: continue | preempt | cancel | merge | defer",
43654
43750
  "Reply with ONLY the decision word, nothing else."
43655
43751
  ].join("\n");
43656
43752
  }
@@ -45272,6 +45368,7 @@ ${notification.stdoutTail}`);
45272
45368
  log17.info(`Agent started: ${this.config.name}`);
45273
45369
  }
45274
45370
  async stop() {
45371
+ this.cancelActiveStream();
45275
45372
  this.attentionController.stop();
45276
45373
  this.heartbeat.stop();
45277
45374
  this._bgCompletionUnsub?.();
@@ -45473,7 +45570,7 @@ ${notification.stdoutTail}`);
45473
45570
  }
45474
45571
  },
45475
45572
  evaluateInterrupt: async (currentItem, newItem) => {
45476
- return this.attentionController.heuristicDecision(currentItem, newItem);
45573
+ return this.attentionController.evaluateWithLLMFallback(currentItem, newItem);
45477
45574
  },
45478
45575
  getTriageContext: async () => {
45479
45576
  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) })) : [];
@@ -45569,7 +45666,7 @@ ${notification.stdoutTail}`);
45569
45666
  case "requirement_update": {
45570
45667
  if (extra.actionRequired) {
45571
45668
  const reqId = item.payload.requirementId ?? "unknown";
45572
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `comment_${reqId}_${ts}`, scenario: "comment_response" }));
45669
+ const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `requirement_${reqId}_${ts}`, scenario: "requirement_action" }));
45573
45670
  resolveResponse(reply);
45574
45671
  return reply;
45575
45672
  }
@@ -45803,6 +45900,7 @@ ${notification.stdoutTail}`);
45803
45900
  if (this.state.status === "offline")
45804
45901
  return;
45805
45902
  this.pauseReason = reason;
45903
+ this.cancelActiveStream();
45806
45904
  this.heartbeat.stop();
45807
45905
  this.attentionController.stop();
45808
45906
  if (this.memoryConsolidationTimer) {
@@ -46633,10 +46731,11 @@ ${report}`);
46633
46731
  if (!this.state.currentActivity) {
46634
46732
  let actType = "chat";
46635
46733
  let actLabel;
46734
+ const peerName = senderInfo?.name || senderId || void 0;
46636
46735
  switch (scenario) {
46637
46736
  case "a2a":
46638
46737
  actType = "a2a";
46639
- actLabel = `Chat from ${senderInfo?.name ?? senderId}`;
46738
+ actLabel = peerName ? `Chat from ${peerName}` : "Agent Message";
46640
46739
  break;
46641
46740
  case "heartbeat":
46642
46741
  actType = "internal";
@@ -46644,14 +46743,14 @@ ${report}`);
46644
46743
  break;
46645
46744
  case "comment_response":
46646
46745
  actType = "chat";
46647
- actLabel = `Comment reply to ${senderInfo?.name ?? senderId ?? "user"}`;
46746
+ actLabel = peerName ? `Comment reply to ${peerName}` : "Comment Reply";
46648
46747
  break;
46649
46748
  case "review":
46650
46749
  actType = "internal";
46651
- actLabel = `Reviewing task from ${senderInfo?.name ?? senderId ?? "worker"}`;
46750
+ actLabel = peerName ? `Reviewing task from ${peerName}` : "Task Review";
46652
46751
  break;
46653
46752
  default:
46654
- actLabel = `Chat with ${senderInfo?.name ?? senderId ?? "user"}`;
46753
+ actLabel = peerName ? `Chat with ${peerName}` : "Human Chat";
46655
46754
  break;
46656
46755
  }
46657
46756
  chatActivityId = this.startActivity(actType, actLabel);
@@ -46894,13 +46993,14 @@ ${block}
46894
46993
  }
46895
46994
  }
46896
46995
  const chatYield = await this.checkAttentionYieldPoint();
46897
- if (chatYield.decision === "preempt" && isPreemptable) {
46898
- log17.info("handleMessage preempted by higher-priority item", {
46996
+ if ((chatYield.decision === "preempt" || chatYield.decision === "cancel") && isPreemptable) {
46997
+ const marker = chatYield.decision === "cancel" ? "[cancelled]" : "[preempted]";
46998
+ log17.info(`handleMessage ${chatYield.decision} by higher-priority item`, {
46899
46999
  agentId: this.id,
46900
47000
  scenario,
46901
47001
  preemptedBy: chatYield.item?.sourceType
46902
47002
  });
46903
- return "[preempted]";
47003
+ return marker;
46904
47004
  }
46905
47005
  if (chatYield.decision === "merge" && chatYield.item) {
46906
47006
  const mergeMsg = `[LIVE UPDATE] ${chatYield.item.payload.summary}
@@ -47081,6 +47181,7 @@ ${chatYield.item.payload.content}`;
47081
47181
  this.checkDailyTokenBudget();
47082
47182
  const llmStart = Date.now();
47083
47183
  let response = await this.withNetworkRetry(() => this.llmRouter.chatStream({ messages, tools: llmTools.length > 0 ? llmTools : void 0, metadata: this.getLLMMetadata(this.currentSessionId), compaction: useCompaction }, wrappedOnEvent, this.getEffectiveProvider(), abortController.signal), "Stream LLM call");
47184
+ streamMarkerDelta.flush();
47084
47185
  const tokensThisCall = response.usage.inputTokens + response.usage.outputTokens;
47085
47186
  this.updateTokensUsed(tokensThisCall);
47086
47187
  this.calibrateTokenCounter(response.usage.inputTokens);
@@ -47231,6 +47332,7 @@ ${chatYield.item.payload.content}`;
47231
47332
  }
47232
47333
  const llmStart2 = Date.now();
47233
47334
  response = await this.withNetworkRetry(() => this.llmRouter.chatStream({ messages: updatedMessages, tools: llmTools.length > 0 ? llmTools : void 0, metadata: this.getLLMMetadata(this.currentSessionId), compaction: useCompaction }, wrappedOnEvent, this.getEffectiveProvider(), abortController.signal), "Stream LLM continuation");
47335
+ streamMarkerDelta.flush();
47234
47336
  const tokens2 = response.usage.inputTokens + response.usage.outputTokens;
47235
47337
  this.updateTokensUsed(tokens2);
47236
47338
  this.calibrateTokenCounter(response.usage.inputTokens);
@@ -47637,14 +47739,16 @@ ${chatYield.item.payload.content}`;
47637
47739
  }
47638
47740
  this.flushPendingInjections(sessionId);
47639
47741
  const yieldResult = await this.checkAttentionYieldPoint();
47640
- if (yieldResult.decision === "preempt") {
47641
- emit("status", "preempted", {
47742
+ if (yieldResult.decision === "preempt" || yieldResult.decision === "cancel") {
47743
+ const statusLabel = yieldResult.decision === "cancel" ? "cancelled" : "preempted";
47744
+ emit("status", statusLabel, {
47642
47745
  reason: yieldResult.reasoning,
47643
47746
  preemptedBy: yieldResult.item?.sourceType
47644
47747
  });
47645
- log17.info("Task preempted by higher-priority mailbox item", {
47748
+ log17.info(`Task ${statusLabel} by higher-priority mailbox item`, {
47646
47749
  taskId: taskId2,
47647
47750
  agentId: this.id,
47751
+ decision: yieldResult.decision,
47648
47752
  preemptedBy: yieldResult.item?.sourceType,
47649
47753
  reasoning: yieldResult.reasoning?.slice(0, 120)
47650
47754
  });
@@ -50359,6 +50463,67 @@ function createA2ATools(ctx) {
50359
50463
  return JSON.stringify({ status: "error", error: String(err) });
50360
50464
  }
50361
50465
  }
50466
+ }] : [],
50467
+ ...ctx.getChannelMessages ? [{
50468
+ name: "recall_context",
50469
+ description: [
50470
+ "Recall historical context you may need to respond effectively.",
50471
+ "Use this BEFORE responding when the provided context is insufficient.",
50472
+ "",
50473
+ "Supported scopes:",
50474
+ '\u2022 "channel" \u2014 Read chat messages from a group chat or DM channel. Requires channel_key.',
50475
+ " Use when you joined a discussion late, were @mentioned, or need to understand prior conversation.",
50476
+ "",
50477
+ "For other context types, use these existing tools instead:",
50478
+ "\u2022 task_get \u2014 Full task details including all comments (scope=task context)",
50479
+ "\u2022 requirement_get \u2014 Full requirement details including all comments (scope=requirement context)",
50480
+ "\u2022 recall_activity \u2014 Your own execution history, activity logs, and past work (scope=your activities)",
50481
+ "\u2022 memory_search \u2014 Your saved memories and notes (scope=personal knowledge)"
50482
+ ].join("\n"),
50483
+ inputSchema: {
50484
+ type: "object",
50485
+ properties: {
50486
+ scope: {
50487
+ type: "string",
50488
+ enum: ["channel"],
50489
+ description: 'Type of context to recall. Currently: "channel" for chat/group messages.'
50490
+ },
50491
+ channel_key: {
50492
+ type: "string",
50493
+ description: 'Required when scope="channel". The channel key (e.g., "group:<teamId>" for team chats, "dm:<id1>_<id2>" for DMs).'
50494
+ },
50495
+ limit: { type: "number", description: "Number of items to fetch (default 30, max 50)." },
50496
+ before: { type: "string", description: "ISO timestamp \u2014 fetch items older than this for pagination. Omit for most recent." }
50497
+ },
50498
+ required: ["scope"]
50499
+ },
50500
+ async execute(args) {
50501
+ const scope = args["scope"];
50502
+ if (scope === "channel") {
50503
+ const channelKey = args["channel_key"];
50504
+ if (!channelKey) {
50505
+ return JSON.stringify({ status: "error", error: 'channel_key is required when scope="channel"' });
50506
+ }
50507
+ const limit = Math.min(args["limit"] ?? 30, 50);
50508
+ const before2 = args["before"];
50509
+ try {
50510
+ const result = await ctx.getChannelMessages(channelKey, limit, before2);
50511
+ const formatted = result.messages.map((m) => `[${m.createdAt}] ${m.senderType === "agent" ? `[agent] ${m.senderName}` : `[human] ${m.senderName}`}: ${m.text.slice(0, 500)}`);
50512
+ return JSON.stringify({
50513
+ messages: formatted,
50514
+ count: result.messages.length,
50515
+ hasMore: result.hasMore,
50516
+ oldestTimestamp: result.messages[0]?.createdAt
50517
+ });
50518
+ } catch (err) {
50519
+ return JSON.stringify({ status: "error", error: String(err) });
50520
+ }
50521
+ }
50522
+ return JSON.stringify({
50523
+ status: "error",
50524
+ error: `Unknown scope: "${scope}". Use "channel" for chat messages, or use task_get / requirement_get / recall_activity for other context.`
50525
+ });
50526
+ }
50362
50527
  }] : []
50363
50528
  ];
50364
50529
  }
@@ -52875,7 +53040,7 @@ var init_dist3 = __esm({
52875
53040
 
52876
53041
  // ../core/dist/agent-manager.js
52877
53042
  import { join as join13 } from "node:path";
52878
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync13, existsSync as existsSync18, copyFileSync, rmSync, readdirSync as readdirSync4 } from "node:fs";
53043
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync13, existsSync as existsSync18, copyFileSync, rmSync, readdirSync as readdirSync4, writeFileSync as writeFileSync11 } from "node:fs";
52879
53044
  import { homedir as homedir6 } from "node:os";
52880
53045
  function resolveCurrentTaskId(agentObj, ts, agentId2) {
52881
53046
  const activeTasks = agentObj?.getActiveTasks?.() ?? [];
@@ -53271,6 +53436,17 @@ You are ${request.name}.`,
53271
53436
  }
53272
53437
  }
53273
53438
  }
53439
+ const heartbeatPath = join13(agentRoleDir, "HEARTBEAT.md");
53440
+ if (!existsSync18(heartbeatPath)) {
53441
+ writeFileSync11(heartbeatPath, [
53442
+ "# Heartbeat Checklist",
53443
+ "",
53444
+ "- [ ] Check mailbox for new messages and respond to urgent items",
53445
+ "- [ ] Review assigned tasks \u2014 update progress, unblock if possible",
53446
+ "- [ ] Check team announcements for new information",
53447
+ "- [ ] Scan recent channel messages for anything requiring attention"
53448
+ ].join("\n"), "utf-8");
53449
+ }
53274
53450
  const config = {
53275
53451
  id,
53276
53452
  name: request.name,
@@ -53436,7 +53612,8 @@ You are ${request.name}.`,
53436
53612
  ...this.groupChatHandlers ? {
53437
53613
  sendGroupMessage: this.groupChatHandlers.sendGroupMessage,
53438
53614
  createGroupChat: (name, memberIds) => this.groupChatHandlers.createGroupChat(name, id, config.name, memberIds),
53439
- listGroupChats: this.groupChatHandlers.listGroupChats
53615
+ listGroupChats: this.groupChatHandlers.listGroupChats,
53616
+ getChannelMessages: this.groupChatHandlers.getChannelMessages
53440
53617
  } : {}
53441
53618
  };
53442
53619
  for (const tool of createA2ATools(a2aContext))
@@ -54009,7 +54186,8 @@ You are ${row.name}.`,
54009
54186
  ...this.groupChatHandlers ? {
54010
54187
  sendGroupMessage: this.groupChatHandlers.sendGroupMessage,
54011
54188
  createGroupChat: (name, memberIds) => this.groupChatHandlers.createGroupChat(name, id, config.name, memberIds),
54012
- listGroupChats: this.groupChatHandlers.listGroupChats
54189
+ listGroupChats: this.groupChatHandlers.listGroupChats,
54190
+ getChannelMessages: this.groupChatHandlers.getChannelMessages
54013
54191
  } : {}
54014
54192
  };
54015
54193
  for (const tool of createA2ATools(a2aCtx))
@@ -55946,7 +56124,7 @@ var init_ollama = __esm({
55946
56124
  });
55947
56125
 
55948
56126
  // ../core/dist/llm/auth-profiles.js
55949
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync13, existsSync as existsSync19, unlinkSync as unlinkSync2 } from "node:fs";
56127
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync12, mkdirSync as mkdirSync13, existsSync as existsSync19, unlinkSync as unlinkSync2 } from "node:fs";
55950
56128
  import { join as join14 } from "node:path";
55951
56129
  import { homedir as homedir7 } from "node:os";
55952
56130
  var log34, AuthProfileStore;
@@ -55977,7 +56155,7 @@ var init_auth_profiles = __esm({
55977
56155
  }
55978
56156
  }
55979
56157
  write(data) {
55980
- writeFileSync11(this.filePath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
56158
+ writeFileSync12(this.filePath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
55981
56159
  }
55982
56160
  /**
55983
56161
  * Acquire a simple filesystem lock for atomic profile updates (token refresh).
@@ -55986,7 +56164,7 @@ var init_auth_profiles = __esm({
55986
56164
  acquireLock() {
55987
56165
  const lockContent = `${process.pid}-${Date.now()}`;
55988
56166
  try {
55989
- writeFileSync11(this.lockPath, lockContent, { flag: "wx" });
56167
+ writeFileSync12(this.lockPath, lockContent, { flag: "wx" });
55990
56168
  return () => {
55991
56169
  try {
55992
56170
  unlinkSync2(this.lockPath);
@@ -55998,7 +56176,7 @@ var init_auth_profiles = __esm({
55998
56176
  const existing = readFileSync14(this.lockPath, "utf-8");
55999
56177
  const ts = Number(existing.split("-").pop());
56000
56178
  if (Date.now() - ts > 3e4) {
56001
- writeFileSync11(this.lockPath, lockContent, { mode: 384 });
56179
+ writeFileSync12(this.lockPath, lockContent, { mode: 384 });
56002
56180
  return () => {
56003
56181
  try {
56004
56182
  unlinkSync2(this.lockPath);
@@ -59348,7 +59526,7 @@ var init_dist4 = __esm({
59348
59526
  });
59349
59527
 
59350
59528
  // ../org-manager/dist/org-service.js
59351
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync12, existsSync as existsSync23, rmSync as rmSync2 } from "node:fs";
59529
+ import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13, existsSync as existsSync23, rmSync as rmSync2 } from "node:fs";
59352
59530
  import { join as join19 } from "node:path";
59353
59531
  import { homedir as homedir10 } from "node:os";
59354
59532
  var log50, OrganizationService;
@@ -59482,15 +59660,15 @@ var init_org_service = __esm({
59482
59660
  mkdirSync15(dir, { recursive: true });
59483
59661
  const annPath = join19(dir, "ANNOUNCEMENT.md");
59484
59662
  if (announcements) {
59485
- writeFileSync12(annPath, announcements, "utf-8");
59663
+ writeFileSync13(annPath, announcements, "utf-8");
59486
59664
  } else if (!existsSync23(annPath)) {
59487
- writeFileSync12(annPath, "", "utf-8");
59665
+ writeFileSync13(annPath, "", "utf-8");
59488
59666
  }
59489
59667
  const normsPath = join19(dir, "NORMS.md");
59490
59668
  if (norms) {
59491
- writeFileSync12(normsPath, norms, "utf-8");
59669
+ writeFileSync13(normsPath, norms, "utf-8");
59492
59670
  } else if (!existsSync23(normsPath)) {
59493
- writeFileSync12(normsPath, "", "utf-8");
59671
+ writeFileSync13(normsPath, "", "utf-8");
59494
59672
  }
59495
59673
  }
59496
59674
  async createTeam(orgId2, name, description) {
@@ -60226,7 +60404,7 @@ var init_org_service = __esm({
60226
60404
  });
60227
60405
 
60228
60406
  // ../org-manager/dist/task-service.js
60229
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync13, readFileSync as readFileSync18, existsSync as existsSync24, cpSync } from "node:fs";
60407
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14, readFileSync as readFileSync18, existsSync as existsSync24, cpSync } from "node:fs";
60230
60408
  import { join as join20, resolve as resolve12 } from "node:path";
60231
60409
  import { homedir as homedir11 } from "node:os";
60232
60410
  function formatLocalTimestamp(d = /* @__PURE__ */ new Date()) {
@@ -60579,7 +60757,7 @@ var init_task_service = __esm({
60579
60757
  mkdirSync16(join20(dir, "knowledge"), { recursive: true });
60580
60758
  const userMdPath = join20(dir, "USER.md");
60581
60759
  if (!existsSync24(userMdPath)) {
60582
- writeFileSync13(userMdPath, [
60760
+ writeFileSync14(userMdPath, [
60583
60761
  "# About the Owner",
60584
60762
  "",
60585
60763
  "- Name:",
@@ -62645,7 +62823,7 @@ Action: ${guidance}` : ""
62645
62823
  testResults: d.testResults
62646
62824
  }))
62647
62825
  };
62648
- writeFileSync13(join20(taskSharedDir, "manifest.json"), JSON.stringify(manifest, null, 2));
62826
+ writeFileSync14(join20(taskSharedDir, "manifest.json"), JSON.stringify(manifest, null, 2));
62649
62827
  for (const d of deliverables) {
62650
62828
  if (d.type === "file" && d.reference) {
62651
62829
  const src = resolve12(d.reference);
@@ -62660,7 +62838,7 @@ Action: ${guidance}` : ""
62660
62838
  }
62661
62839
  if (d.type === "file" && d.summary && !existsSync24(d.reference)) {
62662
62840
  const safeName = d.reference.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
62663
- writeFileSync13(join20(taskSharedDir, `${safeName}.md`), d.summary);
62841
+ writeFileSync14(join20(taskSharedDir, `${safeName}.md`), d.summary);
62664
62842
  }
62665
62843
  }
62666
62844
  log51.info("Deliverables published to shared workspace", { taskId: task.id, dir: taskSharedDir });
@@ -63486,7 +63664,7 @@ ${task.description}`;
63486
63664
 
63487
63665
  // ../org-manager/dist/builder-service.js
63488
63666
  import { join as join21 } from "node:path";
63489
- import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync25, writeFileSync as writeFileSync14, mkdirSync as mkdirSync17, copyFileSync as copyFileSync2, statSync as statSync4, cpSync as cpSync2 } from "node:fs";
63667
+ import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync25, writeFileSync as writeFileSync15, mkdirSync as mkdirSync17, copyFileSync as copyFileSync2, statSync as statSync4, cpSync as cpSync2 } from "node:fs";
63490
63668
  import { homedir as homedir12 } from "node:os";
63491
63669
  var log52, FS_HELPER, BuilderService;
63492
63670
  var init_builder_service = __esm({
@@ -63592,7 +63770,7 @@ var init_builder_service = __esm({
63592
63770
  copyFileSync2(srcFile, join21(agentRoleDir, fname));
63593
63771
  }
63594
63772
  }
63595
- writeFileSync14(join21(agentRoleDir, ".role-origin.json"), JSON.stringify({ customRole: true, source: "builder-artifact", artifact: artifactName, artifactType: "agent" }));
63773
+ writeFileSync15(join21(agentRoleDir, ".role-origin.json"), JSON.stringify({ customRole: true, source: "builder-artifact", artifact: artifactName, artifactType: "agent" }));
63596
63774
  agent.reloadRole();
63597
63775
  await agentManager.startAgent(agent.id);
63598
63776
  return {
@@ -63654,7 +63832,7 @@ var init_builder_service = __esm({
63654
63832
  }
63655
63833
  }
63656
63834
  }
63657
- writeFileSync14(join21(agentRoleDir, ".role-origin.json"), JSON.stringify({ customRole: true, source: "builder-artifact", artifact: artifactName, artifactType: "team" }));
63835
+ writeFileSync15(join21(agentRoleDir, ".role-origin.json"), JSON.stringify({ customRole: true, source: "builder-artifact", artifact: artifactName, artifactType: "team" }));
63658
63836
  agent.reloadRole();
63659
63837
  if (memberRole === "manager") {
63660
63838
  await this.orgService.updateTeam(team.id, { managerId: agent.id, managerType: "agent" });
@@ -64369,23 +64547,6 @@ var init_sse_handler = __esm({
64369
64547
  this.sseBuffer.sendProgress(this.processedTokens, this.totalTokens, `\u5DE5\u5177\u6267\u884C\u5B8C\u6210: ${event.tool}`);
64370
64548
  }
64371
64549
  } else if (event.type === "message_end") {
64372
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
64373
- let turnThinking;
64374
- if (this.thinkingBuf) {
64375
- this.sseBuffer.send({ type: "thinking_commit", thinking: this.thinkingBuf, createdAt: now2 });
64376
- turnThinking = this.thinkingBuf;
64377
- this.thinkingBuf = "";
64378
- }
64379
- if (this.textBuf) {
64380
- this.sseBuffer.send({ type: "text_commit", text: this.textBuf, createdAt: now2 });
64381
- const seg2 = { type: "text", content: this.textBuf, createdAt: now2 };
64382
- if (turnThinking)
64383
- seg2.thinking = turnThinking;
64384
- this.msgSegments.push(seg2);
64385
- this.textBuf = "";
64386
- } else if (turnThinking) {
64387
- this.msgSegments.push({ type: "text", content: "", thinking: turnThinking, createdAt: now2 });
64388
- }
64389
64550
  if (event.usage?.outputTokens) {
64390
64551
  this.totalTokens = Math.max(this.totalTokens, event.usage.outputTokens);
64391
64552
  this.processedTokens = event.usage.outputTokens;
@@ -64459,7 +64620,7 @@ var init_sse_handler = __esm({
64459
64620
 
64460
64621
  // ../org-manager/dist/skill-service.js
64461
64622
  import { join as join22, resolve as resolve13 } from "node:path";
64462
- import { existsSync as existsSync26, writeFileSync as writeFileSync15, mkdirSync as mkdirSync18, readFileSync as readFileSync20, readdirSync as readdirSync9, copyFileSync as copyFileSync3 } from "node:fs";
64623
+ import { existsSync as existsSync26, writeFileSync as writeFileSync16, mkdirSync as mkdirSync18, readFileSync as readFileSync20, readdirSync as readdirSync9, copyFileSync as copyFileSync3 } from "node:fs";
64463
64624
  import { homedir as homedir13 } from "node:os";
64464
64625
  import { execSync as execSync2 } from "node:child_process";
64465
64626
  async function searchSkillHub(query2) {
@@ -64610,7 +64771,7 @@ async function installSkill(request, skillRegistry) {
64610
64771
  if (zipResp.ok) {
64611
64772
  const tmpZip = join22(skillsDir, `_tmp_${safeName}.zip`);
64612
64773
  const buffer = Buffer.from(await zipResp.arrayBuffer());
64613
- writeFileSync15(tmpZip, buffer);
64774
+ writeFileSync16(tmpZip, buffer);
64614
64775
  mkdirSync18(targetDir, { recursive: true });
64615
64776
  try {
64616
64777
  execSync2(`unzip -o "${tmpZip}" -d "${targetDir}"`, { timeout: 3e4 });
@@ -64636,7 +64797,7 @@ async function installSkill(request, skillRegistry) {
64636
64797
  const mdResp = await fetch(skillMdUrl, { signal: AbortSignal.timeout(15e3) });
64637
64798
  if (mdResp.ok) {
64638
64799
  mkdirSync18(targetDir, { recursive: true });
64639
- writeFileSync15(join22(targetDir, "SKILL.md"), await mdResp.text(), "utf-8");
64800
+ writeFileSync16(join22(targetDir, "SKILL.md"), await mdResp.text(), "utf-8");
64640
64801
  installed = true;
64641
64802
  installMethod = "github-skillmd";
64642
64803
  }
@@ -64650,7 +64811,7 @@ async function installSkill(request, skillRegistry) {
64650
64811
  const mdResp = await fetch(rootMd, { signal: AbortSignal.timeout(15e3) });
64651
64812
  if (mdResp.ok) {
64652
64813
  mkdirSync18(targetDir, { recursive: true });
64653
- writeFileSync15(join22(targetDir, "SKILL.md"), await mdResp.text(), "utf-8");
64814
+ writeFileSync16(join22(targetDir, "SKILL.md"), await mdResp.text(), "utf-8");
64654
64815
  installed = true;
64655
64816
  installMethod = "github-root-skillmd";
64656
64817
  }
@@ -64666,7 +64827,7 @@ async function installSkill(request, skillRegistry) {
64666
64827
  if (zipResp.ok) {
64667
64828
  const tmpZip = join22(skillsDir, `_tmp_${safeName}.zip`);
64668
64829
  const buffer = Buffer.from(await zipResp.arrayBuffer());
64669
- writeFileSync15(tmpZip, buffer);
64830
+ writeFileSync16(tmpZip, buffer);
64670
64831
  mkdirSync18(targetDir, { recursive: true });
64671
64832
  try {
64672
64833
  execSync2(`unzip -o "${tmpZip}" -d "${targetDir}"`, { timeout: 3e4 });
@@ -64698,13 +64859,13 @@ async function installSkill(request, skillRegistry) {
64698
64859
  };
64699
64860
  const manifest = buildManifest("skill", raw);
64700
64861
  manifest.source = skillSource;
64701
- writeFileSync15(skillMfPath, JSON.stringify(manifest, null, 2), "utf-8");
64862
+ writeFileSync16(skillMfPath, JSON.stringify(manifest, null, 2), "utf-8");
64702
64863
  } else {
64703
64864
  try {
64704
64865
  const mf = JSON.parse(readFileSync20(skillMfPath, "utf-8"));
64705
64866
  if (!mf.source) {
64706
64867
  mf.source = skillSource;
64707
- writeFileSync15(skillMfPath, JSON.stringify(mf, null, 2), "utf-8");
64868
+ writeFileSync16(skillMfPath, JSON.stringify(mf, null, 2), "utf-8");
64708
64869
  }
64709
64870
  } catch {
64710
64871
  }
@@ -64738,7 +64899,7 @@ var init_skill_service = __esm({
64738
64899
  // ../org-manager/dist/api-server.js
64739
64900
  import { createServer as createServer2 } from "node:http";
64740
64901
  import { join as join23, resolve as resolve14, dirname as dirname6 } from "node:path";
64741
- import { readdirSync as readdirSync10, readFileSync as readFileSync21, existsSync as existsSync27, writeFileSync as writeFileSync16, mkdirSync as mkdirSync19, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
64902
+ import { readdirSync as readdirSync10, readFileSync as readFileSync21, existsSync as existsSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
64742
64903
  import { homedir as homedir14 } from "node:os";
64743
64904
  import { execSync as execSync3 } from "node:child_process";
64744
64905
  async function signToken(payload, secret) {
@@ -64828,7 +64989,7 @@ var init_api_server = __esm({
64828
64989
  init_skill_service();
64829
64990
  log57 = createLogger("api-server");
64830
64991
  PBKDF2_ITERATIONS = 1e4;
64831
- APIServer = class {
64992
+ APIServer = class _APIServer {
64832
64993
  orgService;
64833
64994
  taskService;
64834
64995
  port;
@@ -64853,7 +65014,7 @@ var init_api_server = __esm({
64853
65014
  builderService;
64854
65015
  workflowEngine;
64855
65016
  teamTemplateRegistry;
64856
- customGroupChats = [];
65017
+ // Custom group chats are now persisted in SQLite via storage.groupChatRepo
64857
65018
  constructor(orgService, taskService, port = 8056) {
64858
65019
  this.orgService = orgService;
64859
65020
  this.taskService = taskService;
@@ -64868,15 +65029,18 @@ var init_api_server = __esm({
64868
65029
  am.setGroupChatHandlers({
64869
65030
  sendGroupMessage: async (channelKey, message, senderId, senderName) => {
64870
65031
  const cleanText = stripInternalBlocks(message);
65032
+ const orgId2 = "default";
65033
+ let persistedMsgId;
64871
65034
  if (this.storage) {
64872
- await this.storage.channelMessageRepo.append({
64873
- orgId: "default",
65035
+ const saved = await this.storage.channelMessageRepo.append({
65036
+ orgId: orgId2,
64874
65037
  channel: channelKey,
64875
65038
  senderId,
64876
65039
  senderType: "agent",
64877
65040
  senderName,
64878
65041
  text: cleanText
64879
65042
  });
65043
+ persistedMsgId = saved.id;
64880
65044
  }
64881
65045
  this.ws.broadcast({
64882
65046
  type: "chat:message",
@@ -64889,41 +65053,81 @@ var init_api_server = __esm({
64889
65053
  },
64890
65054
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
64891
65055
  });
64892
- const chat = this.customGroupChats.find((c) => c.id === channelKey);
64893
- if (chat) {
64894
- for (const memberId of chat.memberIds) {
64895
- if (memberId === senderId)
64896
- continue;
64897
- try {
64898
- const memberAgent = am.getAgent(memberId);
64899
- memberAgent.enqueueToMailbox("a2a_message", {
64900
- summary: `Group chat message from ${senderName}`,
64901
- content: `[Group chat: ${chat.name}] ${senderName}: ${cleanText}`,
64902
- extra: { senderId, senderName, channelKey }
64903
- });
64904
- } catch {
65056
+ let allAgentIds = [];
65057
+ if (channelKey.startsWith("group:custom:") && this.storage?.groupChatRepo) {
65058
+ allAgentIds = this.storage.groupChatRepo.getAgentMemberIds(channelKey);
65059
+ } else if (channelKey.startsWith("group:")) {
65060
+ const teamId = channelKey.replace(/^group:/, "");
65061
+ const team = this.orgService.getTeam(teamId);
65062
+ allAgentIds = team?.memberAgentIds ?? [];
65063
+ }
65064
+ const peerAgentIds = allAgentIds.filter((id) => id !== senderId);
65065
+ if (peerAgentIds.length > 0) {
65066
+ const agentManager = this.orgService.getAgentManager();
65067
+ const nameMap = this.buildAgentNameMap(allAgentIds, agentManager);
65068
+ const mentionedNames = this.parseAgentMentions(cleanText, [...nameMap.keys()]);
65069
+ const filteredMentions = mentionedNames.filter((n) => {
65070
+ const id = nameMap.get(n);
65071
+ return id && id !== senderId;
65072
+ });
65073
+ if (filteredMentions.length > 0) {
65074
+ let channelContext = [];
65075
+ if (this.storage) {
65076
+ try {
65077
+ const recent = await this.storage.channelMessageRepo.getMessages(channelKey, 20);
65078
+ channelContext = (recent.messages ?? []).map((m) => ({
65079
+ role: m.senderType === "agent" ? "assistant" : "user",
65080
+ content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
65081
+ }));
65082
+ } catch {
65083
+ }
65084
+ }
65085
+ const roundId = `round_${Date.now()}`;
65086
+ const respondedAgents = /* @__PURE__ */ new Set([senderId]);
65087
+ void this.triggerAgentToAgentChain(filteredMentions, nameMap, senderName, senderId, cleanText, persistedMsgId, channelKey, orgId2, agentManager, allAgentIds.length, { roundId, depth: 1, respondedAgents, allAgentIds });
65088
+ } else {
65089
+ for (const peerId of peerAgentIds) {
65090
+ try {
65091
+ agentManager.getAgent(peerId).enqueueToMailbox("a2a_message", {
65092
+ summary: `Group chat message from ${senderName}`,
65093
+ content: `[Group chat message from ${senderName}]:
65094
+ ${cleanText}`,
65095
+ extra: { senderId, senderName, channelKey }
65096
+ }, {
65097
+ metadata: { senderId, senderName, senderRole: "agent" }
65098
+ });
65099
+ } catch {
65100
+ }
64905
65101
  }
64906
65102
  }
64907
65103
  }
64908
65104
  return "Message sent to group chat";
64909
65105
  },
64910
65106
  createGroupChat: async (name, creatorId, creatorName, memberIds) => {
64911
- const chatId = `group:custom:${Date.now().toString(36)}`;
64912
- this.customGroupChats.push({
64913
- id: chatId,
64914
- name,
64915
- orgId: "default",
64916
- creatorId,
64917
- creatorName,
64918
- memberIds,
64919
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
64920
- });
64921
- this.ws.broadcast({
64922
- type: "chat:group_created",
64923
- payload: { chatId, name, creatorId, creatorName },
64924
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
64925
- });
64926
- return { id: chatId, name };
65107
+ if (this.storage?.groupChatRepo) {
65108
+ const members = memberIds.map((id) => {
65109
+ try {
65110
+ const agentInfo = this.orgService.getAgentManager().getAgent(id);
65111
+ return { id, type: "agent", name: agentInfo?.config?.name ?? id };
65112
+ } catch {
65113
+ return { id, type: "agent", name: id };
65114
+ }
65115
+ });
65116
+ const gc = this.storage.groupChatRepo.create({
65117
+ orgId: "default",
65118
+ name,
65119
+ creatorId,
65120
+ creatorName,
65121
+ members
65122
+ });
65123
+ this.ws.broadcast({
65124
+ type: "chat:group_created",
65125
+ payload: { chatId: gc.channelKey, name, creatorId, creatorName },
65126
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
65127
+ });
65128
+ return { id: gc.channelKey, name };
65129
+ }
65130
+ return { id: `group:custom:${Date.now().toString(36)}`, name };
64927
65131
  },
64928
65132
  listGroupChats: async () => {
64929
65133
  const teams = this.orgService.listTeamsWithMembers("default");
@@ -64933,13 +65137,27 @@ var init_api_server = __esm({
64933
65137
  type: "team",
64934
65138
  channelKey: `group:${t.id}`
64935
65139
  }));
64936
- const customChats = this.customGroupChats.map((c) => ({
64937
- id: c.id,
65140
+ const customChats = this.storage?.groupChatRepo ? this.storage.groupChatRepo.list("default").map((c) => ({
65141
+ id: c.channelKey,
64938
65142
  name: c.name,
64939
65143
  type: "custom",
64940
- channelKey: c.id
64941
- }));
65144
+ channelKey: c.channelKey
65145
+ })) : [];
64942
65146
  return [...teamChats, ...customChats];
65147
+ },
65148
+ getChannelMessages: async (channelKey, limit, before2) => {
65149
+ if (!this.storage)
65150
+ return { messages: [], hasMore: false };
65151
+ const result = this.storage.channelMessageRepo.getMessages(channelKey, limit, before2);
65152
+ return {
65153
+ messages: result.messages.map((m) => ({
65154
+ senderName: m.senderName,
65155
+ senderType: m.senderType,
65156
+ text: stripInternalBlocks(m.text),
65157
+ createdAt: m.createdAt instanceof Date ? m.createdAt.toISOString() : String(m.createdAt)
65158
+ })),
65159
+ hasMore: result.hasMore
65160
+ };
64943
65161
  }
64944
65162
  });
64945
65163
  }
@@ -65005,10 +65223,10 @@ var init_api_server = __esm({
65005
65223
  for (const [fname, content] of Object.entries(data.files)) {
65006
65224
  const filePath = join23(artDir, fname);
65007
65225
  mkdirSync19(dirname6(filePath), { recursive: true });
65008
- writeFileSync16(filePath, content, "utf-8");
65226
+ writeFileSync17(filePath, content, "utf-8");
65009
65227
  }
65010
65228
  } else if (data.config) {
65011
- writeFileSync16(join23(artDir, manifestFilename(mode)), JSON.stringify(data.config, null, 2), "utf-8");
65229
+ writeFileSync17(join23(artDir, manifestFilename(mode)), JSON.stringify(data.config, null, 2), "utf-8");
65012
65230
  }
65013
65231
  return self.builderService.installArtifact(mode, slug);
65014
65232
  }
@@ -65239,48 +65457,167 @@ var init_api_server = __esm({
65239
65457
  log57.warn("Failed to persist chat turn", { error: String(err) });
65240
65458
  }
65241
65459
  }
65460
+ // ── Agent-to-agent reply storm prevention ───────────────────────────────────
65461
+ static A2A_MAX_DEPTH = 3;
65462
+ static A2A_COOLDOWN_MS = 3e4;
65463
+ /** Per-channel cooldown tracker: channel → agentId → last reply timestamp */
65464
+ a2aCooldowns = /* @__PURE__ */ new Map();
65465
+ /** Extract @mentions from agent reply text. Returns agent names found.
65466
+ * Handles multi-word names (e.g. "Ryan 莱恩") by checking known names after each @ sign.
65467
+ * Also matches partial names: "@Sofia" matches "Sofia 索菲亚" if "Sofia" is a token in the name. */
65468
+ parseAgentMentions(text, knownAgentNames) {
65469
+ const mentioned = /* @__PURE__ */ new Set();
65470
+ const sorted = [...knownAgentNames].sort((a, b) => b.length - a.length);
65471
+ const tokenToName = /* @__PURE__ */ new Map();
65472
+ for (const name of sorted) {
65473
+ for (const token of name.split(/\s+/)) {
65474
+ if (token.length >= 2) {
65475
+ const key2 = token.toLowerCase();
65476
+ if (!tokenToName.has(key2))
65477
+ tokenToName.set(key2, name);
65478
+ }
65479
+ }
65480
+ }
65481
+ let idx = 0;
65482
+ while (idx < text.length) {
65483
+ const atPos = text.indexOf("@", idx);
65484
+ if (atPos < 0)
65485
+ break;
65486
+ if (text[atPos + 1] === "[") {
65487
+ const close = text.indexOf("]", atPos + 2);
65488
+ if (close > atPos + 2) {
65489
+ const bracketed = text.slice(atPos + 2, close);
65490
+ const match = sorted.find((n) => n.toLowerCase() === bracketed.toLowerCase());
65491
+ if (match)
65492
+ mentioned.add(match);
65493
+ idx = close + 1;
65494
+ continue;
65495
+ }
65496
+ }
65497
+ const after2 = text.slice(atPos + 1);
65498
+ const afterLower = after2.toLowerCase();
65499
+ const fullMatch = sorted.find((n) => afterLower.startsWith(n.toLowerCase()));
65500
+ if (fullMatch) {
65501
+ mentioned.add(fullMatch);
65502
+ idx = atPos + 1 + fullMatch.length;
65503
+ continue;
65504
+ }
65505
+ const tokenMatch = after2.match(/^(\S+)/);
65506
+ if (tokenMatch) {
65507
+ const token = tokenMatch[1].toLowerCase().replace(/[,,。!?!?;;::、()()[\]【】]+$/, "");
65508
+ const resolved = tokenToName.get(token);
65509
+ if (resolved) {
65510
+ mentioned.add(resolved);
65511
+ idx = atPos + 1 + tokenMatch[1].length;
65512
+ continue;
65513
+ }
65514
+ }
65515
+ idx = atPos + 1;
65516
+ }
65517
+ return [...mentioned];
65518
+ }
65519
+ /** Build name→id lookup for agents in a channel */
65520
+ buildAgentNameMap(agentIds, agentManager) {
65521
+ const map = /* @__PURE__ */ new Map();
65522
+ for (const id of agentIds) {
65523
+ try {
65524
+ map.set(agentManager.getAgent(id).config.name, id);
65525
+ } catch {
65526
+ }
65527
+ }
65528
+ return map;
65529
+ }
65242
65530
  /**
65243
65531
  * Process a single agent's reply in a group chat broadcast.
65244
65532
  * Each agent decides independently whether to respond.
65245
65533
  * Replies are persisted and broadcast via WebSocket.
65534
+ * If the agent @mentions other agents, a chain reply is triggered (with storm prevention).
65246
65535
  */
65247
- async processGroupChatReply(agentId2, userMessage, senderId, senderInfo, channel, orgId2, channelContext, agentManager, teamSize) {
65536
+ async processGroupChatReply(agentId2, userMessage, senderId, senderInfo, channel, orgId2, channelContext, agentManager, teamSize, opts, chainCtx) {
65248
65537
  try {
65249
65538
  const agent = agentManager.getAgent(agentId2);
65250
65539
  const agentName = agent.config.name;
65251
- const groupChatPrefix = [
65252
- `[GROUP CHAT \u2014 ${teamSize} team members receiving this independently]`,
65253
- "",
65254
- "You are ONE of several agents in a group chat. Rules:",
65255
- "1. **Check channel history first** \u2014 if another agent already answered the question or addressed the topic, do NOT repeat their answer. Add only if you have a *different* perspective or expertise.",
65256
- '2. **Only respond if your role/expertise is specifically relevant.** Generic offers like "Let me know if you need help" add no value.',
65257
- "3. **Be concise** \u2014 this is a multi-party conversation. Short, actionable responses only.",
65258
- "4. **Do NOT create tasks** unless the user explicitly asks you to and you are the manager/coordinator.",
65259
- "5. **Prefer [NO_RESPONSE]** \u2014 when in doubt, say nothing. It is better to stay silent than to add noise.",
65260
- "",
65261
- "If you have nothing uniquely valuable to contribute, respond with exactly: [NO_RESPONSE]",
65262
- "---",
65540
+ const isA2A = !!chainCtx && chainCtx.depth > 0;
65541
+ const hasReplyTarget = !!opts?.replyToAgentName;
65542
+ const hasMentions = !!opts?.mentionedNames?.length;
65543
+ const isTargeted = hasReplyTarget || hasMentions;
65544
+ const targetNames = /* @__PURE__ */ new Set();
65545
+ if (opts?.replyToAgentName)
65546
+ targetNames.add(opts.replyToAgentName);
65547
+ if (opts?.mentionedNames)
65548
+ opts.mentionedNames.forEach((n) => targetNames.add(n));
65549
+ const thisAgentIsTarget = targetNames.has(agentName);
65550
+ const prefixLines = [
65551
+ `[GROUP CHAT \u2014 ${teamSize} team members | You are: ${agentName}]`,
65263
65552
  ""
65264
- ].join("\n");
65553
+ ];
65554
+ if (isA2A) {
65555
+ prefixLines.push(`[AGENT COLLABORATION] This message is from a fellow agent (${opts?.replyToAgentName ?? "teammate"}), not from the user.`);
65556
+ prefixLines.push("You were @mentioned because your expertise is needed. Respond concisely to the specific request.");
65557
+ prefixLines.push("You may @mention another agent if (and ONLY if) you genuinely need their specific expertise to answer.");
65558
+ prefixLines.push("Do NOT @mention agents just to be polite or to pass the conversation along.");
65559
+ prefixLines.push("");
65560
+ }
65561
+ if (isTargeted && !isA2A) {
65562
+ if (hasReplyTarget) {
65563
+ prefixLines.push(`[REPLY] The user is replying to ${opts.replyToAgentName}'s message: "${(opts.replyToText ?? "").slice(0, 200)}"`);
65564
+ }
65565
+ if (hasMentions) {
65566
+ prefixLines.push(`[MENTIONED] The user @mentioned: ${opts.mentionedNames.join(", ")}`);
65567
+ }
65568
+ prefixLines.push("");
65569
+ if (thisAgentIsTarget) {
65570
+ prefixLines.push(">>> You are the target of this message. You SHOULD respond. <<<");
65571
+ } else {
65572
+ prefixLines.push(">>> STOP. This message is NOT for you. The user is talking to " + [...targetNames].join(", ") + ", not you.");
65573
+ prefixLines.push("You MUST respond with exactly: [NO_RESPONSE]");
65574
+ prefixLines.push('The ONLY exception: you are directly contradicted by a factual error. Offering opinions, agreement, "me too", or generic help does NOT count. <<<');
65575
+ }
65576
+ } else if (!isA2A) {
65577
+ prefixLines.push("This is an open group message (no specific @mention or reply target).");
65578
+ }
65579
+ prefixLines.push("");
65580
+ prefixLines.push("Rules for ALL group chat messages:");
65581
+ prefixLines.push("1. Check channel history \u2014 if another agent already answered, do NOT repeat.");
65582
+ prefixLines.push("2. Only respond if your specific role/expertise is directly relevant.");
65583
+ prefixLines.push("3. Be concise \u2014 short, actionable responses only.");
65584
+ if (agent.config.agentRole === "manager") {
65585
+ prefixLines.push("4. When assigning work to agents, you MUST use `task_create` to formalize each assignment as a tracked task. Verbal delegation without a task is NOT allowed \u2014 oral promises are meaningless without task tracking. Every commitment must have a corresponding task.");
65586
+ } else {
65587
+ prefixLines.push("4. When you accept a work assignment, verify a task has been created for it (`task_list`). If the coordinator did not create one, remind them or create it yourself with the correct `assigned_agent_id`. Do NOT make promises without task tracking.");
65588
+ }
65589
+ prefixLines.push("5. DEFAULT IS SILENCE. When in doubt, respond with exactly: [NO_RESPONSE]");
65590
+ prefixLines.push('6. If the provided context is insufficient, use `recall_context` (scope="channel") to fetch earlier messages before responding. For task/requirement details use `task_get`/`requirement_get`. Do NOT guess about prior discussion.');
65591
+ if (isTargeted && !thisAgentIsTarget && !isA2A) {
65592
+ prefixLines.push("7. REMINDER: This message is directed at " + [...targetNames].join(", ") + ". You are " + agentName + ". Respond ONLY with [NO_RESPONSE].");
65593
+ }
65594
+ prefixLines.push("---", "");
65595
+ const groupChatPrefix = prefixLines.join("\n");
65265
65596
  const toolEvents = [];
65266
65597
  const reply = await agent.sendMessage(groupChatPrefix + userMessage, senderId, senderInfo, {
65267
- sourceType: "human_chat",
65598
+ sourceType: isA2A ? "a2a_message" : "human_chat",
65599
+ scenario: isA2A ? "a2a" : void 0,
65268
65600
  channelContext,
65269
65601
  toolEventCollector: toolEvents
65270
65602
  });
65271
- if (!reply || reply.trim() === "[NO_RESPONSE]" || !reply.trim()) {
65603
+ if (!reply || !reply.trim() || reply.includes("[NO_RESPONSE]")) {
65272
65604
  return;
65273
65605
  }
65274
- const { thinking, clean: cleanReply } = extractThinkBlocks(reply);
65275
- if (!cleanReply.trim() || cleanReply.trim() === "[NO_RESPONSE]")
65606
+ const { thinking, clean: rawClean } = extractThinkBlocks(reply);
65607
+ const cleanReply = rawClean.replace(/\[NO_RESPONSE\]/gi, "").trim();
65608
+ if (!cleanReply)
65276
65609
  return;
65277
65610
  const metadata = {};
65278
65611
  if (thinking.length > 0)
65279
65612
  metadata["thinking"] = thinking;
65280
65613
  if (toolEvents.length > 0)
65281
65614
  metadata["toolCalls"] = toolEvents;
65615
+ if (isA2A && opts?.replyToAgentName) {
65616
+ metadata["replyToAgent"] = opts.replyToAgentName;
65617
+ }
65618
+ let persistedMsgId;
65282
65619
  if (this.storage) {
65283
- await this.storage.channelMessageRepo.append({
65620
+ const saved = await this.storage.channelMessageRepo.append({
65284
65621
  orgId: orgId2,
65285
65622
  channel,
65286
65623
  senderId: agentId2,
@@ -65288,8 +65625,10 @@ var init_api_server = __esm({
65288
65625
  senderName: agentName,
65289
65626
  text: cleanReply,
65290
65627
  mentions: [],
65291
- metadata: Object.keys(metadata).length > 0 ? metadata : void 0
65628
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
65629
+ replyToId: isA2A ? opts?.replyToMsgId : void 0
65292
65630
  });
65631
+ persistedMsgId = saved.id;
65293
65632
  }
65294
65633
  this.ws.broadcast({
65295
65634
  type: "chat:message",
@@ -65299,10 +65638,29 @@ var init_api_server = __esm({
65299
65638
  senderType: "agent",
65300
65639
  senderName: agentName,
65301
65640
  text: cleanReply,
65302
- metadata: Object.keys(metadata).length > 0 ? metadata : void 0
65641
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
65642
+ ...isA2A ? {
65643
+ replyToId: opts?.replyToMsgId,
65644
+ replyToSender: opts?.replyToAgentName,
65645
+ replyToText: (opts?.replyToText ?? "").slice(0, 120)
65646
+ } : {}
65303
65647
  },
65304
65648
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
65305
65649
  });
65650
+ const allAgentIds = chainCtx?.allAgentIds ?? [];
65651
+ if (allAgentIds.length > 0) {
65652
+ const nameMap = this.buildAgentNameMap(allAgentIds, agentManager);
65653
+ const mentionedNames = this.parseAgentMentions(cleanReply, [...nameMap.keys()]);
65654
+ const filteredMentions = mentionedNames.filter((n) => n !== agentName);
65655
+ log57.info("A2A mention scan", { agentName, mentionedNames, filteredMentions, depth: chainCtx?.depth ?? 0 });
65656
+ if (filteredMentions.length > 0) {
65657
+ const depth = (chainCtx?.depth ?? 0) + 1;
65658
+ const roundId = chainCtx?.roundId ?? `round_${Date.now()}`;
65659
+ const responded = chainCtx?.respondedAgents ?? /* @__PURE__ */ new Set();
65660
+ responded.add(agentId2);
65661
+ void this.triggerAgentToAgentChain(filteredMentions, nameMap, agentName, agentId2, cleanReply, persistedMsgId, channel, orgId2, agentManager, teamSize, { roundId, depth, respondedAgents: responded, allAgentIds });
65662
+ }
65663
+ }
65306
65664
  } catch (err) {
65307
65665
  log57.warn("Group chat agent reply failed", { agentId: agentId2, error: String(err) });
65308
65666
  if (this.storage) {
@@ -65322,6 +65680,56 @@ var init_api_server = __esm({
65322
65680
  }
65323
65681
  }
65324
65682
  }
65683
+ /**
65684
+ * Trigger agent-to-agent chain replies with 3-layer storm prevention:
65685
+ * 1. Depth limit (max 3 hops)
65686
+ * 2. Per-round dedup (each agent responds at most once per conversation round)
65687
+ * 3. Cooldown window (same agent won't be triggered twice within 30s on same channel)
65688
+ */
65689
+ async triggerAgentToAgentChain(mentionedNames, nameMap, fromAgentName, fromAgentId, fromText, fromMsgId, channel, orgId2, agentManager, teamSize, chainCtx) {
65690
+ if (chainCtx.depth > _APIServer.A2A_MAX_DEPTH) {
65691
+ log57.info("A2A chain depth limit reached", { channel, depth: chainCtx.depth, roundId: chainCtx.roundId });
65692
+ return;
65693
+ }
65694
+ const now2 = Date.now();
65695
+ if (!this.a2aCooldowns.has(channel)) {
65696
+ this.a2aCooldowns.set(channel, /* @__PURE__ */ new Map());
65697
+ }
65698
+ const channelCooldowns = this.a2aCooldowns.get(channel);
65699
+ let channelContext = [];
65700
+ if (this.storage) {
65701
+ try {
65702
+ const recent = await this.storage.channelMessageRepo.getMessages(channel, 20);
65703
+ channelContext = (recent.messages ?? []).map((m) => ({
65704
+ role: m.senderType === "agent" ? "assistant" : "user",
65705
+ content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
65706
+ }));
65707
+ } catch {
65708
+ }
65709
+ }
65710
+ for (const targetName of mentionedNames) {
65711
+ const targetId = nameMap.get(targetName);
65712
+ if (!targetId)
65713
+ continue;
65714
+ if (chainCtx.respondedAgents.has(targetId)) {
65715
+ log57.info("A2A skipping (already responded this round)", { targetName, roundId: chainCtx.roundId });
65716
+ continue;
65717
+ }
65718
+ const lastReply = channelCooldowns.get(targetId) ?? 0;
65719
+ if (now2 - lastReply < _APIServer.A2A_COOLDOWN_MS) {
65720
+ log57.info("A2A skipping (cooldown)", { targetName, channel, cooldownRemaining: _APIServer.A2A_COOLDOWN_MS - (now2 - lastReply) });
65721
+ continue;
65722
+ }
65723
+ channelCooldowns.set(targetId, now2);
65724
+ const a2aMessage = `[From @${fromAgentName}]: ${fromText}`;
65725
+ void this.processGroupChatReply(targetId, a2aMessage, fromAgentId, { name: fromAgentName, role: "agent" }, channel, orgId2, channelContext, agentManager, teamSize, {
65726
+ mentionedNames: [targetName],
65727
+ replyToAgentName: fromAgentName,
65728
+ replyToText: fromText.slice(0, 200),
65729
+ replyToMsgId: fromMsgId
65730
+ }, chainCtx);
65731
+ }
65732
+ }
65325
65733
  /** Persist the user message first (before LLM), returns session id for subsequent assistant persistence.
65326
65734
  * When sessionId is provided, appends to that session; when null/undefined, creates a new session. */
65327
65735
  async persistUserMessage(agentId2, userMessage, senderId, images, sessionId) {
@@ -65577,7 +65985,7 @@ var init_api_server = __esm({
65577
65985
  const avatarDir = join23(homedir14(), ".markus", "avatars");
65578
65986
  mkdirSync19(avatarDir, { recursive: true });
65579
65987
  const filename = `${targetType}_${targetId}.${ext}`;
65580
- writeFileSync16(join23(avatarDir, filename), buf);
65988
+ writeFileSync17(join23(avatarDir, filename), buf);
65581
65989
  const avatarUrl = `/api/avatars/${filename}`;
65582
65990
  if (targetType === "user" && this.storage) {
65583
65991
  this.storage.userRepo.updateAvatarUrl(targetId, avatarUrl);
@@ -65654,6 +66062,7 @@ var init_api_server = __esm({
65654
66062
  const senderName = body["senderName"] ?? "You";
65655
66063
  const mentions = body["mentions"] ?? [];
65656
66064
  const targetAgentId = body["targetAgentId"];
66065
+ const replyToId = body["replyToId"];
65657
66066
  const orgId2 = body["orgId"] ?? "default";
65658
66067
  let userMsg;
65659
66068
  if (this.storage) {
@@ -65664,7 +66073,8 @@ var init_api_server = __esm({
65664
66073
  senderType: "human",
65665
66074
  senderName,
65666
66075
  text,
65667
- mentions
66076
+ mentions,
66077
+ replyToId
65668
66078
  });
65669
66079
  }
65670
66080
  const humanOnly = body["humanOnly"] === true;
@@ -65682,21 +66092,50 @@ var init_api_server = __esm({
65682
66092
  return [];
65683
66093
  }
65684
66094
  };
65685
- if (!isHumanChannel && channel.startsWith("group:") && !targetAgentId) {
65686
- const teamId = channel.replace(/^group:/, "");
65687
- const team = this.orgService.getTeam(teamId);
65688
- const allAgentIds = team?.memberAgentIds ?? [];
66095
+ let replyToAgentName;
66096
+ let replyToText;
66097
+ if (replyToId && this.storage) {
66098
+ try {
66099
+ const original = this.storage.channelMessageRepo.getMessageById(replyToId);
66100
+ if (original) {
66101
+ replyToAgentName = original.senderName;
66102
+ replyToText = original.text.slice(0, 200);
66103
+ }
66104
+ } catch {
66105
+ }
66106
+ }
66107
+ const enrichedUserMsg = userMsg ? {
66108
+ ...userMsg,
66109
+ ...replyToAgentName ? { replyToSender: replyToAgentName, replyToText } : {}
66110
+ } : null;
66111
+ if (!isHumanChannel && channel.startsWith("group:")) {
66112
+ let allAgentIds;
66113
+ if (channel.startsWith("group:custom:")) {
66114
+ allAgentIds = this.storage?.groupChatRepo?.getAgentMemberIds(channel) ?? [];
66115
+ } else {
66116
+ const teamId = channel.replace(/^group:/, "");
66117
+ const team = this.orgService.getTeam(teamId);
66118
+ allAgentIds = team?.memberAgentIds ?? [];
66119
+ }
65689
66120
  if (allAgentIds.length === 0) {
65690
- this.json(res, 200, { userMessage: userMsg ?? null, agentMessage: null });
66121
+ this.json(res, 200, { userMessage: enrichedUserMsg, agentMessage: null });
65691
66122
  return;
65692
66123
  }
65693
66124
  const channelContext2 = await buildChannelContext();
65694
66125
  const senderInfo2 = this.orgService.resolveHumanIdentity(senderId);
65695
66126
  const agentManager = this.orgService.getAgentManager();
66127
+ const broadcastOpts = {
66128
+ mentionedNames: mentions.length > 0 ? mentions : void 0,
66129
+ replyToAgentName,
66130
+ replyToText
66131
+ };
66132
+ const roundId = `round_${Date.now()}`;
66133
+ const respondedAgents = /* @__PURE__ */ new Set();
66134
+ const chainCtx = { roundId, depth: 0, respondedAgents, allAgentIds };
65696
66135
  for (const agentId2 of allAgentIds) {
65697
- void this.processGroupChatReply(agentId2, text, senderId, senderInfo2, channel, orgId2, channelContext2, agentManager, allAgentIds.length);
66136
+ void this.processGroupChatReply(agentId2, text, senderId, senderInfo2, channel, orgId2, channelContext2, agentManager, allAgentIds.length, broadcastOpts, chainCtx);
65698
66137
  }
65699
- this.json(res, 200, { userMessage: userMsg ?? null, agentMessage: null });
66138
+ this.json(res, 200, { userMessage: enrichedUserMsg, agentMessage: null });
65700
66139
  return;
65701
66140
  }
65702
66141
  let routedAgentId = null;
@@ -65710,7 +66149,7 @@ var init_api_server = __esm({
65710
66149
  }
65711
66150
  }
65712
66151
  if (!routedAgentId) {
65713
- this.json(res, 200, { userMessage: userMsg ?? null, agentMessage: null });
66152
+ this.json(res, 200, { userMessage: enrichedUserMsg, agentMessage: null });
65714
66153
  return;
65715
66154
  }
65716
66155
  const agent = this.orgService.getAgentManager().getAgent(routedAgentId);
@@ -65753,7 +66192,7 @@ var init_api_server = __esm({
65753
66192
  }
65754
66193
  const statusCode = raw.includes("402") ? 402 : raw.includes("401") ? 401 : raw.includes("429") ? 429 : 502;
65755
66194
  this.json(res, statusCode, {
65756
- userMessage: userMsg ?? null,
66195
+ userMessage: enrichedUserMsg,
65757
66196
  agentMessage: errorMsg ?? null,
65758
66197
  error: detail2
65759
66198
  });
@@ -65780,7 +66219,7 @@ var init_api_server = __esm({
65780
66219
  void this.persistChatTurn(routedAgentId, text, reply, senderId);
65781
66220
  }
65782
66221
  this.json(res, 200, {
65783
- userMessage: userMsg ?? null,
66222
+ userMessage: enrichedUserMsg,
65784
66223
  agentMessage: agentMsg ?? {
65785
66224
  id: `tmp_${Date.now()}`,
65786
66225
  channel,
@@ -65980,7 +66419,7 @@ var init_api_server = __esm({
65980
66419
  if (path === "/api/group-chats" && req.method === "GET") {
65981
66420
  const orgId2 = url.searchParams.get("orgId") ?? "default";
65982
66421
  const teams = this.orgService.listTeamsWithMembers(orgId2);
65983
- const groupChats = teams.map((t) => ({
66422
+ const teamChats = teams.map((t) => ({
65984
66423
  id: `group:${t.id}`,
65985
66424
  name: t.name,
65986
66425
  type: "team",
@@ -65988,21 +66427,16 @@ var init_api_server = __esm({
65988
66427
  memberCount: t.members.length,
65989
66428
  channelKey: `group:${t.id}`
65990
66429
  }));
65991
- const customChats = this.customGroupChats.filter((c) => c.orgId === orgId2);
65992
- this.json(res, 200, {
65993
- chats: [
65994
- ...groupChats,
65995
- ...customChats.map((c) => ({
65996
- id: c.id,
65997
- name: c.name,
65998
- type: "custom",
65999
- creatorId: c.creatorId,
66000
- creatorName: c.creatorName,
66001
- memberCount: c.memberIds?.length ?? 0,
66002
- channelKey: c.id
66003
- }))
66004
- ]
66005
- });
66430
+ const customChats = this.storage?.groupChatRepo ? this.storage.groupChatRepo.list(orgId2).map((c) => ({
66431
+ id: c.id,
66432
+ name: c.name,
66433
+ type: "custom",
66434
+ creatorId: c.creatorId,
66435
+ creatorName: c.creatorName,
66436
+ memberCount: c.memberCount,
66437
+ channelKey: c.channelKey
66438
+ })) : [];
66439
+ this.json(res, 200, { chats: [...teamChats, ...customChats] });
66006
66440
  return;
66007
66441
  }
66008
66442
  if (path === "/api/group-chats" && req.method === "POST") {
@@ -66012,31 +66446,129 @@ var init_api_server = __esm({
66012
66446
  const creatorId = body["creatorId"];
66013
66447
  const creatorName = body["creatorName"];
66014
66448
  const memberIds = body["memberIds"];
66449
+ const memberTypes = body["memberTypes"];
66450
+ const memberNames = body["memberNames"];
66015
66451
  if (!name) {
66016
66452
  this.json(res, 400, { error: "name is required" });
66017
66453
  return;
66018
66454
  }
66019
- const chatId = `group:custom:${Date.now().toString(36)}`;
66020
- const chat = {
66021
- id: chatId,
66022
- name,
66023
- orgId: orgId2,
66024
- creatorId,
66025
- creatorName,
66026
- memberIds: memberIds ?? [],
66027
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
66028
- };
66029
- this.customGroupChats.push(chat);
66455
+ if (!this.storage?.groupChatRepo) {
66456
+ this.json(res, 500, { error: "Storage not initialized" });
66457
+ return;
66458
+ }
66459
+ const members = (memberIds ?? []).map((id) => {
66460
+ const mType = memberTypes?.[id] ?? "agent";
66461
+ let mName = memberNames?.[id] ?? id;
66462
+ if (mType === "agent") {
66463
+ try {
66464
+ mName = this.orgService.getAgentManager().getAgent(id)?.config?.name ?? mName;
66465
+ } catch {
66466
+ }
66467
+ }
66468
+ return { id, type: mType, name: mName };
66469
+ });
66470
+ const gc = this.storage.groupChatRepo.create({ orgId: orgId2, name, creatorId, creatorName, members });
66030
66471
  this.ws?.broadcast({
66031
66472
  type: "chat:group_created",
66032
- payload: { chatId, name, creatorId, creatorName },
66473
+ payload: { chatId: gc.channelKey, name, creatorId, creatorName },
66033
66474
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
66034
66475
  });
66035
66476
  this.json(res, 201, {
66036
- chat: { id: chatId, name, type: "custom", creatorId, creatorName, channelKey: chatId }
66477
+ chat: {
66478
+ id: gc.id,
66479
+ name: gc.name,
66480
+ type: "custom",
66481
+ creatorId: gc.creatorId,
66482
+ creatorName: gc.creatorName,
66483
+ channelKey: gc.channelKey,
66484
+ memberCount: gc.members.length,
66485
+ members: gc.members.map((m) => ({ id: m.memberId, name: m.memberName, type: m.memberType }))
66486
+ }
66037
66487
  });
66038
66488
  return;
66039
66489
  }
66490
+ if (path.match(/^\/api\/group-chats\/[^/]+$/) && req.method === "GET") {
66491
+ const gcId = path.split("/").pop();
66492
+ if (!this.storage?.groupChatRepo) {
66493
+ this.json(res, 500, { error: "Storage not initialized" });
66494
+ return;
66495
+ }
66496
+ const gc = this.storage.groupChatRepo.getById(gcId);
66497
+ if (!gc) {
66498
+ this.json(res, 404, { error: "Group chat not found" });
66499
+ return;
66500
+ }
66501
+ this.json(res, 200, {
66502
+ chat: {
66503
+ id: gc.id,
66504
+ name: gc.name,
66505
+ type: "custom",
66506
+ channelKey: gc.channelKey,
66507
+ creatorId: gc.creatorId,
66508
+ creatorName: gc.creatorName,
66509
+ members: gc.members.map((m) => ({ id: m.memberId, name: m.memberName, type: m.memberType }))
66510
+ }
66511
+ });
66512
+ return;
66513
+ }
66514
+ if (path.match(/^\/api\/group-chats\/[^/]+$/) && req.method === "PATCH") {
66515
+ const gcId = path.split("/").pop();
66516
+ if (!this.storage?.groupChatRepo) {
66517
+ this.json(res, 500, { error: "Storage not initialized" });
66518
+ return;
66519
+ }
66520
+ const body = await this.readBody(req);
66521
+ const newName = body["name"];
66522
+ if (newName)
66523
+ this.storage.groupChatRepo.updateName(gcId, newName);
66524
+ this.json(res, 200, { ok: true });
66525
+ return;
66526
+ }
66527
+ if (path.match(/^\/api\/group-chats\/[^/]+$/) && req.method === "DELETE") {
66528
+ const gcId = path.split("/").pop();
66529
+ if (!this.storage?.groupChatRepo) {
66530
+ this.json(res, 500, { error: "Storage not initialized" });
66531
+ return;
66532
+ }
66533
+ this.storage.groupChatRepo.delete(gcId);
66534
+ this.ws?.broadcast({ type: "chat:group_deleted", payload: { chatId: gcId }, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
66535
+ this.json(res, 200, { ok: true });
66536
+ return;
66537
+ }
66538
+ if (path.match(/^\/api\/group-chats\/[^/]+\/members$/) && req.method === "POST") {
66539
+ const gcId = path.split("/")[3];
66540
+ if (!this.storage?.groupChatRepo) {
66541
+ this.json(res, 500, { error: "Storage not initialized" });
66542
+ return;
66543
+ }
66544
+ const body = await this.readBody(req);
66545
+ const memberId = body["memberId"];
66546
+ const memberType = body["memberType"] ?? "agent";
66547
+ let memberName = body["memberName"] ?? memberId;
66548
+ if (memberType === "agent") {
66549
+ try {
66550
+ memberName = this.orgService.getAgentManager().getAgent(memberId)?.config?.name ?? memberName;
66551
+ } catch {
66552
+ }
66553
+ }
66554
+ this.storage.groupChatRepo.addMember(gcId, memberId, memberType, memberName);
66555
+ this.ws?.broadcast({ type: "chat:group_updated", payload: { chatId: gcId }, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
66556
+ this.json(res, 200, { ok: true });
66557
+ return;
66558
+ }
66559
+ if (path.match(/^\/api\/group-chats\/[^/]+\/members\/[^/]+$/) && req.method === "DELETE") {
66560
+ const parts = path.split("/");
66561
+ const gcId = parts[3];
66562
+ const memberId = parts[5];
66563
+ if (!this.storage?.groupChatRepo) {
66564
+ this.json(res, 500, { error: "Storage not initialized" });
66565
+ return;
66566
+ }
66567
+ this.storage.groupChatRepo.removeMember(gcId, memberId);
66568
+ this.ws?.broadcast({ type: "chat:group_updated", payload: { chatId: gcId }, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
66569
+ this.json(res, 200, { ok: true });
66570
+ return;
66571
+ }
66040
66572
  if (path === "/api/teams" && req.method === "GET") {
66041
66573
  const orgId2 = url.searchParams.get("orgId") ?? "default";
66042
66574
  const teams = this.orgService.listTeamsWithMembers(orgId2);
@@ -66271,7 +66803,7 @@ var init_api_server = __esm({
66271
66803
  const content = body["content"];
66272
66804
  const dir = this.orgService.getTeamDataDir(teamId);
66273
66805
  mkdirSync19(dir, { recursive: true });
66274
- writeFileSync16(join23(dir, filename), content ?? "", "utf-8");
66806
+ writeFileSync17(join23(dir, filename), content ?? "", "utf-8");
66275
66807
  this.json(res, 200, { ok: true });
66276
66808
  return;
66277
66809
  }
@@ -67116,7 +67648,7 @@ var init_api_server = __esm({
67116
67648
  }
67117
67649
  const body = await this.readBody(req);
67118
67650
  const content = body["content"] ?? "";
67119
- writeFileSync16(join23(roleDir, filename), content, "utf-8");
67651
+ writeFileSync17(join23(roleDir, filename), content, "utf-8");
67120
67652
  this.json(res, 200, { ok: true });
67121
67653
  } catch {
67122
67654
  this.json(res, 404, { error: `Agent not found: ${agentId2}` });
@@ -68455,7 +68987,7 @@ EXPLANATION_END`;
68455
68987
  const mfName = manifestFilename(pkgType);
68456
68988
  const artDir = join23(homedir14(), ".markus", "builder-artifacts", typeDir, manifest.name);
68457
68989
  mkdirSync19(artDir, { recursive: true });
68458
- writeFileSync16(join23(artDir, mfName), JSON.stringify(manifest, null, 2), "utf-8");
68990
+ writeFileSync17(join23(artDir, mfName), JSON.stringify(manifest, null, 2), "utf-8");
68459
68991
  const artFiles = artifact.files;
68460
68992
  if (artFiles) {
68461
68993
  for (const [fn, c] of Object.entries(artFiles)) {
@@ -68463,18 +68995,18 @@ EXPLANATION_END`;
68463
68995
  continue;
68464
68996
  const filePath = join23(artDir, fn);
68465
68997
  mkdirSync19(dirname6(filePath), { recursive: true });
68466
- writeFileSync16(filePath, c, "utf-8");
68998
+ writeFileSync17(filePath, c, "utf-8");
68467
68999
  }
68468
69000
  }
68469
69001
  if (mode === "team") {
68470
69002
  const fileSet = new Set(artFiles ? Object.keys(artFiles) : []);
68471
69003
  const announcement = artifact.announcement;
68472
69004
  if (announcement && !fileSet.has("ANNOUNCEMENT.md")) {
68473
- writeFileSync16(join23(artDir, "ANNOUNCEMENT.md"), announcement, "utf-8");
69005
+ writeFileSync17(join23(artDir, "ANNOUNCEMENT.md"), announcement, "utf-8");
68474
69006
  }
68475
69007
  const norms = artifact.norms;
68476
69008
  if (norms && !fileSet.has("NORMS.md")) {
68477
- writeFileSync16(join23(artDir, "NORMS.md"), norms, "utf-8");
69009
+ writeFileSync17(join23(artDir, "NORMS.md"), norms, "utf-8");
68478
69010
  }
68479
69011
  const rawMembers = Array.isArray(artifact.team?.members) ? artifact.team.members : Array.isArray(artifact.members) ? artifact.members : [];
68480
69012
  for (const m of rawMembers) {
@@ -68486,15 +69018,15 @@ EXPLANATION_END`;
68486
69018
  const contextContent = m.contextContent || m.context_md;
68487
69019
  if (roleContent && !fileSet.has(`members/${slug}/ROLE.md`)) {
68488
69020
  mkdirSync19(memberDir, { recursive: true });
68489
- writeFileSync16(join23(memberDir, "ROLE.md"), roleContent, "utf-8");
69021
+ writeFileSync17(join23(memberDir, "ROLE.md"), roleContent, "utf-8");
68490
69022
  }
68491
69023
  if (policiesContent && !fileSet.has(`members/${slug}/POLICIES.md`)) {
68492
69024
  mkdirSync19(memberDir, { recursive: true });
68493
- writeFileSync16(join23(memberDir, "POLICIES.md"), policiesContent, "utf-8");
69025
+ writeFileSync17(join23(memberDir, "POLICIES.md"), policiesContent, "utf-8");
68494
69026
  }
68495
69027
  if (contextContent && !fileSet.has(`members/${slug}/CONTEXT.md`)) {
68496
69028
  mkdirSync19(memberDir, { recursive: true });
68497
- writeFileSync16(join23(memberDir, "CONTEXT.md"), contextContent, "utf-8");
69029
+ writeFileSync17(join23(memberDir, "CONTEXT.md"), contextContent, "utf-8");
68498
69030
  }
68499
69031
  }
68500
69032
  const memberFiles = artifact.memberFiles;
@@ -68503,7 +69035,7 @@ EXPLANATION_END`;
68503
69035
  const memberDir = join23(artDir, "members", slug);
68504
69036
  mkdirSync19(memberDir, { recursive: true });
68505
69037
  for (const [fn, c] of Object.entries(files))
68506
- writeFileSync16(join23(memberDir, fn), c, "utf-8");
69038
+ writeFileSync17(join23(memberDir, fn), c, "utf-8");
68507
69039
  }
68508
69040
  }
68509
69041
  }
@@ -68541,7 +69073,7 @@ EXPLANATION_END`;
68541
69073
  for (const [fn, content] of Object.entries(files)) {
68542
69074
  const filePath = join23(artDir, fn);
68543
69075
  mkdirSync19(dirname6(filePath), { recursive: true });
68544
- writeFileSync16(filePath, content, "utf-8");
69076
+ writeFileSync17(filePath, content, "utf-8");
68545
69077
  }
68546
69078
  if (source) {
68547
69079
  const mfName = manifestFilename(type);
@@ -68550,7 +69082,7 @@ EXPLANATION_END`;
68550
69082
  try {
68551
69083
  const mf = JSON.parse(readFileSync21(mfPath, "utf-8"));
68552
69084
  mf.source = source;
68553
- writeFileSync16(mfPath, JSON.stringify(mf, null, 2), "utf-8");
69085
+ writeFileSync17(mfPath, JSON.stringify(mf, null, 2), "utf-8");
68554
69086
  } catch {
68555
69087
  }
68556
69088
  }
@@ -69351,7 +69883,7 @@ EXPLANATION_END`;
69351
69883
  try {
69352
69884
  if (token) {
69353
69885
  mkdirSync19(join23(homedir14(), ".markus"), { recursive: true });
69354
- writeFileSync16(tokenPath, token, "utf-8");
69886
+ writeFileSync17(tokenPath, token, "utf-8");
69355
69887
  } else if (existsSync27(tokenPath)) {
69356
69888
  rmSync3(tokenPath);
69357
69889
  }
@@ -72643,11 +73175,13 @@ var init_requirement_service = __esm({
72643
73175
  req.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
72644
73176
  }
72645
73177
  /**
72646
- * Check if all tasks for a requirement are done. If so, mark it as completed.
73178
+ * Check if all tasks for a requirement are done. Instead of auto-completing,
73179
+ * notify the creator agent so they can review results and decide whether to
73180
+ * complete the requirement or create additional tasks.
72647
73181
  */
72648
73182
  checkCompletion(requirementId2, taskStatuses) {
72649
73183
  const req = this.requirements.get(requirementId2);
72650
- if (!req || req.status === "completed")
73184
+ if (!req || req.status === "completed" || req.status === "cancelled")
72651
73185
  return false;
72652
73186
  if (req.taskIds.length === 0)
72653
73187
  return false;
@@ -72655,14 +73189,28 @@ var init_requirement_service = __esm({
72655
73189
  const status = taskStatuses.get(tid);
72656
73190
  return status === "completed" || status === "cancelled" || status === "archived";
72657
73191
  });
72658
- if (allDone) {
72659
- req.status = "completed";
72660
- req.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
72661
- if (this.requirementRepo) {
72662
- this.requirementRepo.updateStatus(requirementId2, "completed").catch((e) => log62.error("Failed to persist requirement completion", { id: requirementId2, error: String(e) }));
73192
+ if (allDone && req.createdBy && this.agentManager) {
73193
+ try {
73194
+ const agent = this.agentManager.getAgent(req.createdBy);
73195
+ if (agent) {
73196
+ agent.enqueueToMailbox("requirement_update", {
73197
+ summary: `All tasks for requirement "${req.title}" are done \u2014 review needed`,
73198
+ content: [
73199
+ `[REQUIREMENT REVIEW NEEDED] All linked tasks for "${req.title}" (ID: ${requirementId2}) have reached terminal state.`,
73200
+ "",
73201
+ "Please review the results and decide:",
73202
+ `1. If the requirement is fully satisfied, complete it: requirement_update_status(requirement_id="${requirementId2}", status="completed")`,
73203
+ `2. If more work is needed, create additional tasks with task_create(requirement_id="${requirementId2}", ...)`
73204
+ ].join("\n"),
73205
+ requirementId: requirementId2,
73206
+ extra: { actionRequired: true }
73207
+ }, {
73208
+ priority: 2,
73209
+ metadata: { senderName: "System", senderRole: "manager" }
73210
+ });
73211
+ }
73212
+ } catch {
72663
73213
  }
72664
- this.broadcast("requirement:completed", req);
72665
- log62.info("Requirement completed", { id: requirementId2 });
72666
73214
  return true;
72667
73215
  }
72668
73216
  return false;
@@ -72806,14 +73354,13 @@ var init_requirement_service = __esm({
72806
73354
  parts.push("2. Abandon this requirement if the feedback indicates it is not needed");
72807
73355
  }
72808
73356
  const agent = this.agentManager.getAgent(creatorId);
72809
- const isRejection = decision === "rejected";
72810
73357
  agent.enqueueToMailbox("requirement_update", {
72811
73358
  summary: `Requirement "${req.title}" ${decision}`,
72812
73359
  content: parts.join("\n"),
72813
73360
  requirementId: req.id,
72814
- extra: isRejection ? { actionRequired: true } : void 0
73361
+ extra: { actionRequired: true }
72815
73362
  }, {
72816
- priority: isRejection ? 1 : 3,
73363
+ priority: 1,
72817
73364
  metadata: { senderName: "System", senderRole: "manager" }
72818
73365
  });
72819
73366
  log62.info("Notified creator agent about requirement decision", {
@@ -73031,7 +73578,7 @@ var init_knowledge_service = __esm({
73031
73578
  });
73032
73579
 
73033
73580
  // ../org-manager/dist/file-knowledge-store.js
73034
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync17, existsSync as existsSync28, mkdirSync as mkdirSync20, readdirSync as readdirSync11, unlinkSync as unlinkSync3 } from "node:fs";
73581
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync18, existsSync as existsSync28, mkdirSync as mkdirSync20, readdirSync as readdirSync11, unlinkSync as unlinkSync3 } from "node:fs";
73035
73582
  import { join as join24 } from "node:path";
73036
73583
  function readdirSafe(dir) {
73037
73584
  try {
@@ -73087,12 +73634,12 @@ var init_file_knowledge_store = __esm({
73087
73634
  saveEntry(entry) {
73088
73635
  const dir = this.scopeDir(entry.scope, entry.scopeId);
73089
73636
  mkdirSync20(dir, { recursive: true });
73090
- writeFileSync17(join24(dir, `${entry.id}.json`), JSON.stringify(entry, null, 2));
73637
+ writeFileSync18(join24(dir, `${entry.id}.json`), JSON.stringify(entry, null, 2));
73091
73638
  }
73092
73639
  saveIndex(scope, scopeId, entries2) {
73093
73640
  const dir = this.scopeDir(scope, scopeId);
73094
73641
  mkdirSync20(dir, { recursive: true });
73095
- writeFileSync17(join24(dir, "_index.json"), JSON.stringify(entries2, null, 2));
73642
+ writeFileSync18(join24(dir, "_index.json"), JSON.stringify(entries2, null, 2));
73096
73643
  }
73097
73644
  removeEntryFile(entry) {
73098
73645
  const p = join24(this.scopeDir(entry.scope, entry.scopeId), `${entry.id}.json`);
@@ -74130,7 +74677,8 @@ function openSqlite(dbPath) {
74130
74677
  { table: "chat_sessions", column: "is_main", sql: "ALTER TABLE chat_sessions ADD COLUMN is_main INTEGER NOT NULL DEFAULT 0" },
74131
74678
  { table: "mailbox_items", column: "retry_count", sql: "ALTER TABLE mailbox_items ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0" },
74132
74679
  { table: "users", column: "avatar_url", sql: "ALTER TABLE users ADD COLUMN avatar_url TEXT" },
74133
- { table: "agents", column: "avatar_url", sql: "ALTER TABLE agents ADD COLUMN avatar_url TEXT" }
74680
+ { table: "agents", column: "avatar_url", sql: "ALTER TABLE agents ADD COLUMN avatar_url TEXT" },
74681
+ { table: "channel_messages", column: "reply_to_id", sql: "ALTER TABLE channel_messages ADD COLUMN reply_to_id TEXT" }
74134
74682
  ];
74135
74683
  for (const m of migrations) {
74136
74684
  const cols = _db.prepare(`PRAGMA table_info(${m.table})`).all();
@@ -74187,7 +74735,7 @@ function migrateToExecutionStreamLogs(db) {
74187
74735
  log71.info(`Migration: copied ${actLogCount} agent_activity_logs to execution_stream_logs`);
74188
74736
  }
74189
74737
  }
74190
- var log71, SCHEMA_SQL, _db, SqliteOrgRepo, SqliteAgentRepo, SqliteTaskRepo, SqliteRequirementRepo, SqliteProjectRepo, SqliteTaskLogRepo, SqliteTaskCommentRepo, SqliteRequirementCommentRepo, SqliteMessageRepo, SqliteChatSessionRepo, SqliteChannelMessageRepo, SqliteUserRepo, SqliteTeamRepo, SqliteMarketplaceTemplateRepo, SqliteMarketplaceSkillRepo, SqliteMarketplaceRatingRepo, SqliteAgentKnowledgeRepo, SqliteExternalAgentRepo, SqliteDeliverableRepo, SqliteActivityRepo, SqliteExecutionStreamRepo, SqliteMailboxRepo, SqliteDecisionRepo, SqliteNotificationRepo, SqliteApprovalRepo;
74738
+ var log71, SCHEMA_SQL, _db, SqliteOrgRepo, SqliteAgentRepo, SqliteTaskRepo, SqliteRequirementRepo, SqliteProjectRepo, SqliteTaskLogRepo, SqliteTaskCommentRepo, SqliteRequirementCommentRepo, SqliteMessageRepo, SqliteChatSessionRepo, SqliteChannelMessageRepo, SqliteUserRepo, SqliteTeamRepo, SqliteMarketplaceTemplateRepo, SqliteMarketplaceSkillRepo, SqliteMarketplaceRatingRepo, SqliteAgentKnowledgeRepo, SqliteExternalAgentRepo, SqliteDeliverableRepo, SqliteActivityRepo, SqliteExecutionStreamRepo, SqliteMailboxRepo, SqliteDecisionRepo, SqliteNotificationRepo, SqliteApprovalRepo, SqliteGroupChatRepo;
74191
74739
  var init_sqlite_storage = __esm({
74192
74740
  "../storage/dist/sqlite-storage.js"() {
74193
74741
  "use strict";
@@ -74325,6 +74873,7 @@ CREATE TABLE IF NOT EXISTS channel_messages (
74325
74873
  text TEXT NOT NULL,
74326
74874
  mentions TEXT NOT NULL DEFAULT '[]',
74327
74875
  metadata TEXT DEFAULT '{}',
74876
+ reply_to_id TEXT,
74328
74877
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
74329
74878
  );
74330
74879
  CREATE INDEX IF NOT EXISTS idx_channel_messages_channel ON channel_messages(channel, created_at);
@@ -74685,6 +75234,26 @@ CREATE TABLE IF NOT EXISTS approvals (
74685
75234
  selected_option TEXT
74686
75235
  );
74687
75236
  CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status, requested_at DESC);
75237
+
75238
+ CREATE TABLE IF NOT EXISTS group_chats (
75239
+ id TEXT PRIMARY KEY,
75240
+ org_id TEXT NOT NULL DEFAULT 'default',
75241
+ name TEXT NOT NULL,
75242
+ channel_key TEXT NOT NULL UNIQUE,
75243
+ creator_id TEXT NOT NULL,
75244
+ creator_name TEXT NOT NULL,
75245
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
75246
+ );
75247
+
75248
+ CREATE TABLE IF NOT EXISTS group_chat_members (
75249
+ group_chat_id TEXT NOT NULL REFERENCES group_chats(id) ON DELETE CASCADE,
75250
+ member_id TEXT NOT NULL,
75251
+ member_type TEXT NOT NULL CHECK(member_type IN ('human', 'agent')),
75252
+ member_name TEXT NOT NULL,
75253
+ added_at TEXT NOT NULL DEFAULT (datetime('now')),
75254
+ PRIMARY KEY (group_chat_id, member_id)
75255
+ );
75256
+ CREATE INDEX IF NOT EXISTS idx_group_chat_members_group ON group_chat_members(group_chat_id);
74688
75257
  `;
74689
75258
  _db = null;
74690
75259
  SqliteOrgRepo = class {
@@ -75553,7 +76122,7 @@ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status, requested_a
75553
76122
  async append(data) {
75554
76123
  const id = generateId2("chm");
75555
76124
  const ts = now();
75556
- this.db.prepare("INSERT INTO channel_messages (id, org_id, channel, sender_id, sender_type, sender_name, text, mentions, metadata, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)").run(id, data.orgId, data.channel, data.senderId, data.senderType, data.senderName, data.text, toJson(data.mentions ?? []), toJson(data.metadata ?? {}), ts);
76125
+ this.db.prepare("INSERT INTO channel_messages (id, org_id, channel, sender_id, sender_type, sender_name, text, mentions, metadata, reply_to_id, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run(id, data.orgId, data.channel, data.senderId, data.senderType, data.senderName, data.text, toJson(data.mentions ?? []), toJson(data.metadata ?? {}), data.replyToId ?? null, ts);
75557
76126
  return {
75558
76127
  id,
75559
76128
  orgId: data.orgId,
@@ -75564,17 +76133,20 @@ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status, requested_a
75564
76133
  text: data.text,
75565
76134
  mentions: data.mentions ?? [],
75566
76135
  metadata: data.metadata ?? null,
76136
+ replyToId: data.replyToId,
75567
76137
  createdAt: new Date(ts)
75568
76138
  };
75569
76139
  }
75570
76140
  getMessages(channel, limit = 50, before2) {
75571
- let q = "SELECT * FROM channel_messages WHERE channel = ?";
76141
+ let q = `SELECT m.*, r.sender_name AS reply_to_sender, SUBSTR(r.text, 1, 120) AS reply_to_text
76142
+ FROM channel_messages m LEFT JOIN channel_messages r ON m.reply_to_id = r.id
76143
+ WHERE m.channel = ?`;
75572
76144
  const vals = [channel];
75573
76145
  if (before2) {
75574
- q += " AND created_at < ?";
76146
+ q += " AND m.created_at < ?";
75575
76147
  vals.push(before2);
75576
76148
  }
75577
- q += " ORDER BY created_at DESC LIMIT ?";
76149
+ q += " ORDER BY m.created_at DESC LIMIT ?";
75578
76150
  vals.push(limit + 1);
75579
76151
  const rows = this.db.prepare(q).all(...vals).map((r) => ({
75580
76152
  id: r["id"],
@@ -75586,11 +76158,26 @@ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status, requested_a
75586
76158
  text: r["text"],
75587
76159
  mentions: fromJson(r["mentions"]),
75588
76160
  metadata: fromJson(r["metadata"]),
76161
+ replyToId: r["reply_to_id"] ?? void 0,
76162
+ replyToSender: r["reply_to_sender"] ?? void 0,
76163
+ replyToText: r["reply_to_text"] ?? void 0,
75589
76164
  createdAt: toDate(r["created_at"])
75590
76165
  }));
75591
76166
  const hasMore = rows.length > limit;
75592
76167
  return { messages: rows.slice(0, limit).reverse(), hasMore };
75593
76168
  }
76169
+ getMessageById(id) {
76170
+ const r = this.db.prepare("SELECT * FROM channel_messages WHERE id = ?").get(id);
76171
+ if (!r)
76172
+ return void 0;
76173
+ return {
76174
+ id: r["id"],
76175
+ senderName: r["sender_name"],
76176
+ senderType: r["sender_type"],
76177
+ senderId: r["sender_id"],
76178
+ text: r["text"]
76179
+ };
76180
+ }
75594
76181
  };
75595
76182
  SqliteUserRepo = class {
75596
76183
  db;
@@ -76708,6 +77295,96 @@ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status, requested_a
76708
77295
  };
76709
77296
  }
76710
77297
  };
77298
+ SqliteGroupChatRepo = class {
77299
+ db;
77300
+ constructor(db) {
77301
+ this.db = db;
77302
+ }
77303
+ create(data) {
77304
+ const id = generateId2("gc");
77305
+ const channelKey = `group:custom:${id}`;
77306
+ const ts = now();
77307
+ this.db.prepare("INSERT INTO group_chats (id, org_id, name, channel_key, creator_id, creator_name, created_at) VALUES (?,?,?,?,?,?,?)").run(id, data.orgId, data.name, channelKey, data.creatorId, data.creatorName, ts);
77308
+ const insertMember = this.db.prepare("INSERT OR IGNORE INTO group_chat_members (group_chat_id, member_id, member_type, member_name, added_at) VALUES (?,?,?,?,?)");
77309
+ const members = [];
77310
+ for (const m of data.members) {
77311
+ insertMember.run(id, m.id, m.type, m.name, ts);
77312
+ members.push({ groupChatId: id, memberId: m.id, memberType: m.type, memberName: m.name, addedAt: new Date(ts) });
77313
+ }
77314
+ return {
77315
+ id,
77316
+ orgId: data.orgId,
77317
+ name: data.name,
77318
+ channelKey,
77319
+ creatorId: data.creatorId,
77320
+ creatorName: data.creatorName,
77321
+ createdAt: new Date(ts),
77322
+ members
77323
+ };
77324
+ }
77325
+ list(orgId2) {
77326
+ const rows = this.db.prepare(`SELECT g.*, (SELECT COUNT(*) FROM group_chat_members WHERE group_chat_id = g.id) AS member_count
77327
+ FROM group_chats g WHERE g.org_id = ? ORDER BY g.created_at DESC`).all(orgId2);
77328
+ return rows.map((r) => ({
77329
+ ...this.mapRow(r),
77330
+ memberCount: r["member_count"]
77331
+ }));
77332
+ }
77333
+ getById(id) {
77334
+ const r = this.db.prepare("SELECT * FROM group_chats WHERE id = ?").get(id);
77335
+ if (!r)
77336
+ return void 0;
77337
+ const members = this.getMembers(id);
77338
+ return { ...this.mapRow(r), members };
77339
+ }
77340
+ getByChannelKey(channelKey) {
77341
+ const r = this.db.prepare("SELECT * FROM group_chats WHERE channel_key = ?").get(channelKey);
77342
+ if (!r)
77343
+ return void 0;
77344
+ const gc = this.mapRow(r);
77345
+ return { ...gc, members: this.getMembers(gc.id) };
77346
+ }
77347
+ getMembers(groupChatId) {
77348
+ const rows = this.db.prepare("SELECT * FROM group_chat_members WHERE group_chat_id = ?").all(groupChatId);
77349
+ return rows.map((r) => ({
77350
+ groupChatId: r["group_chat_id"],
77351
+ memberId: r["member_id"],
77352
+ memberType: r["member_type"],
77353
+ memberName: r["member_name"],
77354
+ addedAt: toDate(r["added_at"])
77355
+ }));
77356
+ }
77357
+ getAgentMemberIds(channelKey) {
77358
+ const gc = this.db.prepare("SELECT id FROM group_chats WHERE channel_key = ?").get(channelKey);
77359
+ if (!gc)
77360
+ return [];
77361
+ const rows = this.db.prepare("SELECT member_id FROM group_chat_members WHERE group_chat_id = ? AND member_type = 'agent'").all(gc.id);
77362
+ return rows.map((r) => r.member_id);
77363
+ }
77364
+ addMember(groupChatId, memberId, memberType, memberName) {
77365
+ this.db.prepare("INSERT OR IGNORE INTO group_chat_members (group_chat_id, member_id, member_type, member_name, added_at) VALUES (?,?,?,?,?)").run(groupChatId, memberId, memberType, memberName, now());
77366
+ }
77367
+ removeMember(groupChatId, memberId) {
77368
+ this.db.prepare("DELETE FROM group_chat_members WHERE group_chat_id = ? AND member_id = ?").run(groupChatId, memberId);
77369
+ }
77370
+ updateName(id, name) {
77371
+ this.db.prepare("UPDATE group_chats SET name = ? WHERE id = ?").run(name, id);
77372
+ }
77373
+ delete(id) {
77374
+ this.db.prepare("DELETE FROM group_chats WHERE id = ?").run(id);
77375
+ }
77376
+ mapRow(r) {
77377
+ return {
77378
+ id: r["id"],
77379
+ orgId: r["org_id"],
77380
+ name: r["name"],
77381
+ channelKey: r["channel_key"],
77382
+ creatorId: r["creator_id"],
77383
+ creatorName: r["creator_name"],
77384
+ createdAt: toDate(r["created_at"])
77385
+ };
77386
+ }
77387
+ };
76711
77388
  }
76712
77389
  });
76713
77390
 
@@ -76724,6 +77401,7 @@ __export(dist_exports3, {
76724
77401
  SqliteDeliverableRepo: () => SqliteDeliverableRepo,
76725
77402
  SqliteExecutionStreamRepo: () => SqliteExecutionStreamRepo,
76726
77403
  SqliteExternalAgentRepo: () => SqliteExternalAgentRepo,
77404
+ SqliteGroupChatRepo: () => SqliteGroupChatRepo,
76727
77405
  SqliteMailboxRepo: () => SqliteMailboxRepo,
76728
77406
  SqliteMarketplaceRatingRepo: () => SqliteMarketplaceRatingRepo,
76729
77407
  SqliteMarketplaceSkillRepo: () => SqliteMarketplaceSkillRepo,
@@ -76793,7 +77471,8 @@ async function initSqliteStorage(url) {
76793
77471
  mailboxRepo: new storage.SqliteMailboxRepo(db),
76794
77472
  decisionRepo: new storage.SqliteDecisionRepo(db),
76795
77473
  notificationRepo: new storage.SqliteNotificationRepo(db),
76796
- approvalRepo: new storage.SqliteApprovalRepo(db)
77474
+ approvalRepo: new storage.SqliteApprovalRepo(db),
77475
+ groupChatRepo: new storage.SqliteGroupChatRepo(db)
76797
77476
  };
76798
77477
  log72.info("SQLite storage initialized", { path: dbPath });
76799
77478
  return bridge;
@@ -77885,7 +78564,7 @@ var init_startupProgress = __esm({
77885
78564
 
77886
78565
  // src/connector-service.ts
77887
78566
  import { resolve as resolve15, join as join28, dirname as dirname8 } from "node:path";
77888
- import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync18, mkdirSync as mkdirSync24, readdirSync as readdirSync12, cpSync as cpSync3 } from "node:fs";
78567
+ import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync19, mkdirSync as mkdirSync24, readdirSync as readdirSync12, cpSync as cpSync3 } from "node:fs";
77889
78568
  import { homedir as homedir18 } from "node:os";
77890
78569
  import { execSync as execSync4 } from "node:child_process";
77891
78570
  import { fileURLToPath as fileURLToPath4 } from "node:url";
@@ -77986,7 +78665,7 @@ function writePlatformConfig(connector, markusUrl, token) {
77986
78665
  setNestedField(config, connector.integration.tokenField, token);
77987
78666
  try {
77988
78667
  const content = JSON.stringify(config, null, 2);
77989
- writeFileSync18(configPath, content, "utf-8");
78668
+ writeFileSync19(configPath, content, "utf-8");
77990
78669
  return true;
77991
78670
  } catch {
77992
78671
  return false;
@@ -78082,7 +78761,7 @@ function registerInitCommand(program2) {
78082
78761
  });
78083
78762
  }
78084
78763
  async function quickInit(options) {
78085
- const { writeFileSync: writeFileSync20, mkdirSync: mkdirSync26 } = await import("node:fs");
78764
+ const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync26 } = await import("node:fs");
78086
78765
  const { join: pathJoin } = await import("node:path");
78087
78766
  const readline4 = await import("node:readline");
78088
78767
  const nonInteractive = options?.nonInteractive ?? false;
@@ -78361,7 +79040,7 @@ async function quickInit(options) {
78361
79040
  const devRoleDir = pathJoin(userTemplatesDir || pathJoin(process.cwd(), "templates"), "roles", "developer");
78362
79041
  if (!existsSync32(devRoleDir)) {
78363
79042
  mkdirSync26(devRoleDir, { recursive: true });
78364
- writeFileSync20(
79043
+ writeFileSync21(
78365
79044
  pathJoin(devRoleDir, "ROLE.md"),
78366
79045
  [
78367
79046
  "---",
@@ -79269,6 +79948,19 @@ ${reason}`;
79269
79948
  }, triageProvider);
79270
79949
  return response.content;
79271
79950
  });
79951
+ agent.getAttentionController().setLLMJudge(async (prompt) => {
79952
+ const response = await llmRouter.chat({
79953
+ messages: [
79954
+ { role: "system", content: 'You are an attention interrupt judge. Decide whether to interrupt current work for a new incoming message. Reply with ONLY one word: continue, preempt, cancel, merge, or defer. Use "preempt" to pause (resume later) and "cancel" to permanently stop current work.' },
79955
+ { role: "user", content: prompt }
79956
+ ],
79957
+ temperature: 0.1,
79958
+ maxTokens: 32
79959
+ }, triageProvider);
79960
+ const raw = response.content.trim().toLowerCase();
79961
+ const valid = ["continue", "preempt", "cancel", "merge", "defer"];
79962
+ return valid.includes(raw) ? raw : "continue";
79963
+ });
79272
79964
  agent.getAttentionController().setTriageChatFn(async (messages, tools) => {
79273
79965
  const llmTools = tools?.map((t) => ({
79274
79966
  name: t.name,
@@ -80295,6 +80987,12 @@ ${C3.BOLD}${C3.CYAN}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
80295
80987
  checkWarn("Skills directory not found", `${skillsDir} \u2014 run: markus skill install <name>`);
80296
80988
  }
80297
80989
  section("Network");
80990
+ const proxyUrl = process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || process.env["https_proxy"] || process.env["http_proxy"];
80991
+ if (proxyUrl) {
80992
+ checkOk("HTTP proxy configured", proxyUrl);
80993
+ } else {
80994
+ checkWarn("HTTP proxy", "not configured \u2014 if behind firewall, set HTTPS_PROXY env var");
80995
+ }
80298
80996
  try {
80299
80997
  const res = await fetch("https://api.anthropic.com/.well-known/ready", {
80300
80998
  method: "HEAD",
@@ -82976,7 +83674,7 @@ var skill_exports = {};
82976
83674
  __export(skill_exports, {
82977
83675
  registerSkillCommands: () => registerSkillCommands
82978
83676
  });
82979
- import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync25 } from "node:fs";
83677
+ import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync25 } from "node:fs";
82980
83678
  import { resolve as resolve18 } from "node:path";
82981
83679
  function registerSkillCommands(program2) {
82982
83680
  const root = program2.command("skill").description("Skills registry and local scaffold");
@@ -83084,8 +83782,8 @@ Describe what this skill does for agents.
83084
83782
  ## Tools / behavior
83085
83783
 
83086
83784
  `;
83087
- writeFileSync19(resolve18(dir, "skill.json"), JSON.stringify(skillJson, null, 2) + "\n");
83088
- writeFileSync19(resolve18(dir, "SKILL.md"), skillMd);
83785
+ writeFileSync20(resolve18(dir, "skill.json"), JSON.stringify(skillJson, null, 2) + "\n");
83786
+ writeFileSync20(resolve18(dir, "SKILL.md"), skillMd);
83089
83787
  if (out.json) {
83090
83788
  console.log(JSON.stringify({ dir, files: ["skill.json", "SKILL.md"] }, null, 2));
83091
83789
  } else {