@desplega.ai/agent-swarm 1.110.0 → 1.112.0

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 (119) hide show
  1. package/README.md +1 -1
  2. package/dist/{actions-837ktebk.js → actions-eckb4g8r.js} +6 -6
  3. package/dist/{app-85h97pg2.js → app-b05sgm02.js} +3 -3
  4. package/dist/{assistant-yhrta0c5.js → assistant-7255sbyw.js} +9 -9
  5. package/dist/{boot-reembed-5sd1t9r4.js → boot-reembed-y1bt3ttn.js} +4 -4
  6. package/dist/{boot-reembed-8xmfv2pq.js → boot-reembed-yv7awg4y.js} +3 -3
  7. package/dist/{boot-scrub-logs-fdw8wbtq.js → boot-scrub-logs-ddyaeqar.js} +2 -2
  8. package/dist/{claude-managed-setup-yhbcx0bz.js → claude-managed-setup-cm05wtek.js} +1 -1
  9. package/dist/{cli-8ps5wsk9.js → cli-0c3n0eba.js} +1 -1
  10. package/dist/{cli-kna0r84e.js → cli-15598a4k.js} +599 -51
  11. package/dist/{cli-8s0swp2t.js → cli-30d2ct3k.js} +3 -3
  12. package/dist/{cli-2h86bsfe.js → cli-3r5y6ayj.js} +2 -2
  13. package/dist/{cli-6ttdv68b.js → cli-52tbq17n.js} +1 -1
  14. package/dist/{cli-k6ssh5dk.js → cli-56s4ehzw.js} +1 -1
  15. package/dist/{cli-m3enfr8q.js → cli-6wscj9z0.js} +4 -4
  16. package/dist/{cli-5kav4x1a.js → cli-709t753c.js} +7 -2
  17. package/dist/{cli-g44ffxk5.js → cli-76d1vtvq.js} +11 -2
  18. package/dist/{cli-gb7dqnna.js → cli-7xkwgkrs.js} +1 -1
  19. package/dist/{cli-kg9v8h1w.js → cli-8f0dwca2.js} +1 -1
  20. package/dist/{cli-zw3cmjh6.js → cli-94vzr3nq.js} +4 -4
  21. package/dist/{cli-d8vmtyyr.js → cli-951w7c79.js} +1 -1
  22. package/dist/{cli-dhph6q98.js → cli-9p0qhead.js} +1 -1
  23. package/dist/{cli-vfyhrk9k.js → cli-a5e8n5hg.js} +1 -1
  24. package/dist/{cli-w6zp4qcx.js → cli-ceewqajw.js} +37 -39
  25. package/dist/{cli-r19ttke3.js → cli-h9jx5mag.js} +4 -4
  26. package/dist/{cli-4trfcacj.js → cli-hm27prrc.js} +5 -5
  27. package/dist/{cli-gtt1f14h.js → cli-pt7c7qxz.js} +3 -3
  28. package/dist/{cli-sjm0e1pa.js → cli-r1btc895.js} +2 -2
  29. package/dist/{cli-3baz70yp.js → cli-rp8pca98.js} +1 -1
  30. package/dist/{cli-dy01kwxk.js → cli-s9pj3c7z.js} +2 -2
  31. package/dist/{cli-0pzg1f8z.js → cli-tch0ypp6.js} +71 -24
  32. package/dist/{cli-3t97r3y1.js → cli-tg0f2wmz.js} +5 -5
  33. package/dist/{cli-amncg1dq.js → cli-tk9vyqzh.js} +66 -9
  34. package/dist/{cli-cwety9qq.js → cli-tp7e60s3.js} +4 -2
  35. package/dist/cli.js +11 -11
  36. package/dist/{codex-adapter-ebs12jef.js → codex-adapter-5cmy858w.js} +3 -3
  37. package/dist/{codex-login-4sywkz6v.js → codex-login-0h18cy3e.js} +1 -1
  38. package/dist/{codex-session-runner-3bx0b1hn.js → codex-session-runner-46q8mrba.js} +3 -3
  39. package/dist/{commands-3fh75nzv.js → commands-14g7taf4.js} +2 -2
  40. package/dist/{db-kwfxkhpw.js → db-3tge4jrn.js} +2 -2
  41. package/dist/{handlers-jj8rzetb.js → handlers-sy64egg6.js} +9 -9
  42. package/dist/{hook-1b45zzz1.js → hook-rnkzw8vp.js} +3 -3
  43. package/dist/{http-71964f7a.js → http-xx9xptcs.js} +234 -37
  44. package/dist/{index-e01wmtj0.js → index-3wr6v2mm.js} +7 -7
  45. package/dist/{index-464v93y6.js → index-brma88d4.js} +9 -9
  46. package/dist/{index-c35g752k.js → index-ha8e9xn2.js} +7 -11
  47. package/dist/{index-zm0x6vmf.js → index-zfx5p959.js} +8 -8
  48. package/dist/{keepalive-2gxqjgrx.js → keepalive-rbz05zad.js} +4 -4
  49. package/dist/{lead-twm4zp8y.js → lead-s790v7r0.js} +20 -20
  50. package/dist/{maintenance-1htsnyz6.js → maintenance-420rmadb.js} +4 -4
  51. package/dist/{onboard-1y1pdzt5.js → onboard-jgqamqnw.js} +2 -2
  52. package/dist/{otel-impl-pw9tnc80.js → otel-impl-ch3n3783.js} +1 -1
  53. package/dist/{pi-mono-adapter-ayr3akk8.js → pi-mono-adapter-kxjxpfgy.js} +2 -2
  54. package/dist/{pricing-refresh-tqvvrk7a.js → pricing-refresh-rmtrby6h.js} +4 -4
  55. package/dist/{seed-pricing-f23sehzq.js → seed-pricing-1szjzfj1.js} +3 -3
  56. package/dist/{setup-jtw9rz9t.js → setup-8jfzkyzv.js} +2 -2
  57. package/dist/{worker-jxrmzwb9.js → worker-40vq0pm8.js} +20 -20
  58. package/openapi.json +1010 -2
  59. package/package.json +1 -1
  60. package/src/be/memory/constants.ts +7 -0
  61. package/src/be/memory/graph-expansion.ts +167 -0
  62. package/src/be/memory/link-resolver.ts +81 -17
  63. package/src/be/memory/links-store.ts +208 -0
  64. package/src/be/memory/providers/sqlite-store.ts +2 -2
  65. package/src/be/memory/raters/retrieval.ts +2 -1
  66. package/src/be/memory/types.ts +1 -1
  67. package/src/be/memory/usefulness-stats.ts +259 -0
  68. package/src/be/migrations/107_memory_usefulness_indexes.sql +12 -0
  69. package/src/be/scripts/typecheck.ts +2 -0
  70. package/src/be/seed-scripts/catalog/smart-recall.ts +1 -5
  71. package/src/commands/runner.ts +154 -8
  72. package/src/github/handlers.ts +128 -25
  73. package/src/github/mentions-aliases.test.ts +4 -3
  74. package/src/http/codex-oauth-keep-warm.ts +107 -0
  75. package/src/http/index.ts +2 -0
  76. package/src/http/memory.ts +73 -6
  77. package/src/providers/codex-adapter.ts +66 -2
  78. package/src/providers/codex-oauth/auth-json.ts +18 -9
  79. package/src/providers/codex-oauth/flow.ts +1 -1
  80. package/src/providers/codex-oauth/storage.ts +88 -6
  81. package/src/providers/codex-oauth/types.ts +1 -1
  82. package/src/scripts-runtime/sdk-allowlist.ts +2 -0
  83. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  84. package/src/server.ts +4 -0
  85. package/src/slack/message-text.ts +25 -0
  86. package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +113 -11
  87. package/src/tests/codex-adapter.test.ts +27 -0
  88. package/src/tests/codex-oauth-keep-warm.test.ts +268 -0
  89. package/src/tests/codex-oauth-refresh-lock.test.ts +4 -4
  90. package/src/tests/codex-oauth-storage.test.ts +149 -6
  91. package/src/tests/codex-oauth.test.ts +12 -0
  92. package/src/tests/create-page-tool.test.ts +7 -0
  93. package/src/tests/entrypoint-codex-oauth-seed.test.ts +124 -0
  94. package/src/tests/github-event-filter.test.ts +3 -2
  95. package/src/tests/github-handlers-inline-comments.test.ts +114 -24
  96. package/src/tests/jira-webhook-lifecycle.test.ts +7 -0
  97. package/src/tests/memory-edit.test.ts +95 -0
  98. package/src/tests/memory-get-tool.test.ts +138 -0
  99. package/src/tests/memory-graph-expansion.test.ts +421 -0
  100. package/src/tests/memory-http-recall-gating.test.ts +13 -0
  101. package/src/tests/memory-link-resolver.test.ts +228 -2
  102. package/src/tests/memory-usefulness-endpoint.test.ts +392 -0
  103. package/src/tests/runner-repo-autostash.test.ts +168 -1
  104. package/src/tests/script-workflows-runtime-e2e.test.ts +1 -0
  105. package/src/tests/slack-delete.test.ts +157 -0
  106. package/src/tests/slack-message-text.test.ts +37 -1
  107. package/src/tests/slack-update.test.ts +167 -0
  108. package/src/tests/slack-upload-file.test.ts +135 -0
  109. package/src/tests/tool-annotations.test.ts +3 -0
  110. package/src/tools/inject-learning.ts +1 -0
  111. package/src/tools/memory-edit.ts +3 -2
  112. package/src/tools/memory-get.ts +66 -1
  113. package/src/tools/memory-search.ts +13 -3
  114. package/src/tools/slack-delete.ts +102 -0
  115. package/src/tools/slack-reply.ts +11 -3
  116. package/src/tools/slack-start-thread.ts +2 -0
  117. package/src/tools/slack-update.ts +127 -0
  118. package/src/tools/slack-upload-file.ts +11 -1
  119. package/src/tools/tool-config.ts +3 -1
@@ -14,13 +14,13 @@ import {
14
14
  getOAuthApp,
15
15
  getOAuthTokens,
16
16
  upsertOAuthApp
17
- } from "./cli-6ttdv68b.js";
17
+ } from "./cli-52tbq17n.js";
18
18
  import {
19
19
  startPricingRefreshLoop
20
- } from "./cli-sjm0e1pa.js";
20
+ } from "./cli-r1btc895.js";
21
21
  import {
22
22
  seedPricingFromModelsDev
23
- } from "./cli-3baz70yp.js";
23
+ } from "./cli-rp8pca98.js";
24
24
  import {
25
25
  createTrackerAgentMapping,
26
26
  createTrackerSync,
@@ -35,7 +35,7 @@ import {
35
35
  repointTrackerSyncBySwarmId,
36
36
  updateTrackerSync,
37
37
  updateTrackerSyncSwarmId
38
- } from "./cli-k6ssh5dk.js";
38
+ } from "./cli-56s4ehzw.js";
39
39
  import {
40
40
  ZodFirstPartyTypeKind,
41
41
  objectType
@@ -45,7 +45,7 @@ import {
45
45
  ensure,
46
46
  require_dist,
47
47
  runScheduleNow
48
- } from "./cli-zw3cmjh6.js";
48
+ } from "./cli-94vzr3nq.js";
49
49
  import {
50
50
  TriggerSchemaError,
51
51
  applyDefinitionPatch,
@@ -57,7 +57,7 @@ import {
57
57
  startWorkflowExecution,
58
58
  validateDefinition,
59
59
  validateJsonSchema
60
- } from "./cli-3t97r3y1.js";
60
+ } from "./cli-tg0f2wmz.js";
61
61
  import {
62
62
  getApiKey
63
63
  } from "./cli-f14fvzag.js";
@@ -73,24 +73,29 @@ import {
73
73
  setScriptConnectionEnabled,
74
74
  upsertCredentialBinding,
75
75
  upsertScriptConnection
76
- } from "./cli-2h86bsfe.js";
76
+ } from "./cli-3r5y6ayj.js";
77
77
  import {
78
78
  CANDIDATE_SET_MULTIPLIER,
79
+ computeScore,
79
80
  getEmbeddingProvider,
80
81
  getMemoryStore,
81
82
  init_constants,
82
83
  init_reranker,
83
- rerank
84
- } from "./cli-5kav4x1a.js";
84
+ init_sqlite_store,
85
+ isGraphExpansionEnabled,
86
+ rerank,
87
+ rowToCandidate
88
+ } from "./cli-709t753c.js";
85
89
  import {
86
90
  createAdditiveBuffer,
87
- extractSlackMessageText
88
- } from "./cli-g44ffxk5.js";
91
+ extractSlackMessageText,
92
+ parseSlackTs
93
+ } from "./cli-76d1vtvq.js";
89
94
  import {
90
95
  getSlackApp,
91
96
  startSlackApp,
92
97
  stopSlackApp
93
- } from "./cli-4trfcacj.js";
98
+ } from "./cli-hm27prrc.js";
94
99
  import {
95
100
  findOrCreateUserByEmail,
96
101
  findUserByEmail,
@@ -102,7 +107,7 @@ import {
102
107
  recordIdentityEvent,
103
108
  resolveUserByToken,
104
109
  unlinkIdentity
105
- } from "./cli-gb7dqnna.js";
110
+ } from "./cli-7xkwgkrs.js";
106
111
  import {
107
112
  markdownToSlack
108
113
  } from "./cli-6xd0bvf8.js";
@@ -113,7 +118,7 @@ import {
113
118
  createTaskWithSiblingAwareness,
114
119
  githubContextKey,
115
120
  linearContextKey
116
- } from "./cli-8ps5wsk9.js";
121
+ } from "./cli-0c3n0eba.js";
117
122
  import {
118
123
  getAppUrl,
119
124
  getMcpBaseUrl,
@@ -342,7 +347,7 @@ import {
342
347
  upsertService,
343
348
  upsertSwarmConfig,
344
349
  validateConfigValue
345
- } from "./cli-d8vmtyyr.js";
350
+ } from "./cli-951w7c79.js";
346
351
  import {
347
352
  init_zod
348
353
  } from "./cli-q21d49ac.js";
@@ -386,7 +391,7 @@ import {
386
391
  import {
387
392
  init_package,
388
393
  package_default
389
- } from "./cli-kg9v8h1w.js";
394
+ } from "./cli-8f0dwca2.js";
390
395
  import {
391
396
  init_secret_scrubber,
392
397
  refreshSecretScrubberCache,
@@ -19668,7 +19673,7 @@ async function initOtel(serviceRole = process.env.AGENT_ROLE || "api") {
19668
19673
  return;
19669
19674
  initialized2 = true;
19670
19675
  try {
19671
- const impl = await import("./otel-impl-pw9tnc80.js");
19676
+ const impl = await import("./otel-impl-ch3n3783.js");
19672
19677
  await impl.boot(serviceRole);
19673
19678
  realWithSpan = impl.withSpan;
19674
19679
  realStartSpan = impl.startSpan;
@@ -21616,7 +21621,8 @@ ${learning}`;
21616
21621
  scope: "swarm",
21617
21622
  name: `Lead feedback: ${category} — ${learning.slice(0, 60)}`,
21618
21623
  content,
21619
- source: "manual"
21624
+ source: "manual",
21625
+ tags: [category]
21620
21626
  });
21621
21627
  try {
21622
21628
  const provider = getEmbeddingProvider();
@@ -23341,6 +23347,24 @@ function resolveWikilinksToMemoryIds(agentId, links) {
23341
23347
  return link;
23342
23348
  });
23343
23349
  }
23350
+ var INSERT_LINK_SQL = `INSERT OR IGNORE INTO memory_link
23351
+ (id, from_memory_id, linkType, targetKind, targetId, strength, resolver, sourceText, metadata, createdAt, updatedAt)
23352
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
23353
+ function insertLinkArgs(memoryId, link, now) {
23354
+ return [
23355
+ crypto.randomUUID(),
23356
+ memoryId,
23357
+ link.linkType,
23358
+ link.targetKind,
23359
+ link.targetId,
23360
+ link.strength,
23361
+ link.resolver,
23362
+ link.sourceText,
23363
+ link.metadata ? JSON.stringify(link.metadata) : null,
23364
+ now,
23365
+ now
23366
+ ];
23367
+ }
23344
23368
  function storeLinks(memoryId, agentId, content) {
23345
23369
  const links = resolveLinks(content);
23346
23370
  if (links.length === 0)
@@ -23348,12 +23372,34 @@ function storeLinks(memoryId, agentId, content) {
23348
23372
  const resolved = resolveWikilinksToMemoryIds(agentId, links);
23349
23373
  const db = getDb();
23350
23374
  const now = new Date().toISOString();
23351
- const insert = db.prepare(`INSERT OR IGNORE INTO memory_link
23352
- (id, from_memory_id, linkType, targetKind, targetId, strength, resolver, sourceText, metadata, createdAt, updatedAt)
23353
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
23375
+ const insert = db.prepare(INSERT_LINK_SQL);
23376
+ db.transaction(() => {
23377
+ for (const link of resolved) {
23378
+ insert.run(...insertLinkArgs(memoryId, link, now));
23379
+ }
23380
+ })();
23381
+ }
23382
+ function linkIdentity2(link) {
23383
+ return JSON.stringify([link.linkType, link.targetKind, link.targetId, link.sourceText ?? null]);
23384
+ }
23385
+ function refreshLinks(memoryId, agentId, content) {
23386
+ const resolved = resolveWikilinksToMemoryIds(agentId, resolveLinks(content));
23387
+ const nextIdentities = new Set(resolved.map((link) => linkIdentity2(link)));
23388
+ const db = getDb();
23389
+ const now = new Date().toISOString();
23390
+ const selectExisting = db.prepare(`SELECT id, linkType, targetKind, targetId, sourceText
23391
+ FROM memory_link
23392
+ WHERE from_memory_id = ? AND linkType != 'sequel'`);
23393
+ const deleteById = db.prepare("DELETE FROM memory_link WHERE id = ?");
23394
+ const insert = db.prepare(INSERT_LINK_SQL);
23354
23395
  db.transaction(() => {
23396
+ for (const row of selectExisting.all(memoryId)) {
23397
+ if (!nextIdentities.has(linkIdentity2(row))) {
23398
+ deleteById.run(row.id);
23399
+ }
23400
+ }
23355
23401
  for (const link of resolved) {
23356
- insert.run(crypto.randomUUID(), memoryId, link.linkType, link.targetKind, link.targetId, link.strength, link.resolver, link.sourceText, link.metadata ? JSON.stringify(link.metadata) : null, now, now);
23402
+ insert.run(...insertLinkArgs(memoryId, link, now));
23357
23403
  }
23358
23404
  })();
23359
23405
  }
@@ -23427,7 +23473,7 @@ var registerMemoryEditTool = (server) => {
23427
23473
  if (embedding)
23428
23474
  store.updateEmbedding(result.memory.id, embedding, provider.name);
23429
23475
  try {
23430
- storeLinks(result.memory.id, requestInfo.agentId, result.memory.content);
23476
+ refreshLinks(result.memory.id, requestInfo.agentId, result.memory.content);
23431
23477
  } catch (err) {
23432
23478
  console.error(`[memory-edit] Link resolution failed for ${result.memory.id}:`, err.message);
23433
23479
  }
@@ -23464,12 +23510,99 @@ var registerMemoryEditTool = (server) => {
23464
23510
 
23465
23511
  // src/tools/memory-get.ts
23466
23512
  init_zod();
23513
+ init_db();
23467
23514
 
23468
23515
  // src/be/memory/access.ts
23469
23516
  function canReadMemory(memory, agentId) {
23470
23517
  return memory.scope === "swarm" || memory.agentId === agentId;
23471
23518
  }
23472
23519
 
23520
+ // src/be/memory/links-store.ts
23521
+ init_db();
23522
+ function redactedTargetId(sourceText) {
23523
+ const wikilink = /^\[\[(.+)\]\]$/.exec(sourceText ?? "");
23524
+ return wikilink?.[1] ?? "";
23525
+ }
23526
+ function getLinksForMemory(memoryId, options = {}) {
23527
+ const { viewerAgentId, isLead = false } = options;
23528
+ const db = getDb();
23529
+ const outgoing = db.prepare(`SELECT ml.id, ml.linkType, ml.targetKind, ml.targetId, ml.strength,
23530
+ ml.resolver, ml.sourceText, ml.createdAt,
23531
+ m.id AS targetMemoryId, m.name AS targetName,
23532
+ m.scope AS targetScope, m.agentId AS targetAgentId
23533
+ FROM memory_link ml
23534
+ LEFT JOIN agent_memory m
23535
+ ON ml.targetKind = 'memory'
23536
+ AND m.id = ml.targetId
23537
+ AND (m.expiresAt IS NULL OR m.expiresAt > datetime('now'))
23538
+ WHERE ml.from_memory_id = ?
23539
+ ORDER BY ml.createdAt ASC, ml.id ASC`).all(memoryId);
23540
+ const links = outgoing.map((row) => {
23541
+ if (row.targetKind !== "memory") {
23542
+ return {
23543
+ id: row.id,
23544
+ linkType: row.linkType,
23545
+ targetKind: row.targetKind,
23546
+ targetId: row.targetId,
23547
+ strength: row.strength,
23548
+ resolver: row.resolver,
23549
+ sourceText: row.sourceText,
23550
+ createdAt: row.createdAt,
23551
+ resolved: true
23552
+ };
23553
+ }
23554
+ const live = row.targetMemoryId !== null;
23555
+ const visible = live && (isLead || row.targetScope === "swarm" || (row.targetAgentId ?? "") === viewerAgentId);
23556
+ const base = {
23557
+ id: row.id,
23558
+ linkType: row.linkType,
23559
+ targetKind: row.targetKind,
23560
+ targetId: visible ? row.targetId : redactedTargetId(row.sourceText),
23561
+ strength: row.strength,
23562
+ resolver: row.resolver,
23563
+ sourceText: row.sourceText,
23564
+ createdAt: row.createdAt,
23565
+ resolved: false
23566
+ };
23567
+ if (!visible)
23568
+ return base;
23569
+ return {
23570
+ ...base,
23571
+ resolved: true,
23572
+ target: {
23573
+ id: row.targetMemoryId,
23574
+ name: row.targetName ?? "",
23575
+ scope: row.targetScope ?? ""
23576
+ }
23577
+ };
23578
+ });
23579
+ const conditions = [
23580
+ "ml.targetKind = 'memory'",
23581
+ "ml.targetId = ?",
23582
+ "(m.expiresAt IS NULL OR m.expiresAt > datetime('now'))"
23583
+ ];
23584
+ const params = [memoryId];
23585
+ if (!isLead) {
23586
+ conditions.push("(m.agentId = ? OR m.scope = 'swarm')");
23587
+ params.push(viewerAgentId ?? null);
23588
+ }
23589
+ const inbound = db.prepare(`SELECT ml.id, ml.linkType, ml.strength, ml.sourceText, ml.createdAt,
23590
+ m.id AS fromId, m.name AS fromName, m.scope AS fromScope
23591
+ FROM memory_link ml
23592
+ JOIN agent_memory m ON m.id = ml.from_memory_id
23593
+ WHERE ${conditions.join(" AND ")}
23594
+ ORDER BY ml.createdAt ASC, ml.id ASC`).all(...params);
23595
+ const backlinks = inbound.map((row) => ({
23596
+ id: row.id,
23597
+ linkType: row.linkType,
23598
+ strength: row.strength,
23599
+ sourceText: row.sourceText,
23600
+ createdAt: row.createdAt,
23601
+ from: { id: row.fromId, name: row.fromName, scope: row.fromScope }
23602
+ }));
23603
+ return { links, backlinks };
23604
+ }
23605
+
23473
23606
  // src/be/memory/raters/retrieval.ts
23474
23607
  init_db();
23475
23608
  function recordRetrievals(taskId, agentId, results, sessionId, extras) {
@@ -23508,6 +23641,31 @@ function getRetrievalsForTask(taskId) {
23508
23641
  // src/tools/memory-get.ts
23509
23642
  init_types();
23510
23643
  var NUDGE_ELIGIBLE_SOURCES = new Set(["manual", "file_index"]);
23644
+ var LinkedMemoryRefSchema = object({
23645
+ id: string(),
23646
+ name: string(),
23647
+ scope: string()
23648
+ });
23649
+ var MemoryLinkSchema = object({
23650
+ id: string(),
23651
+ linkType: string(),
23652
+ targetKind: string(),
23653
+ targetId: string(),
23654
+ strength: number(),
23655
+ resolver: string(),
23656
+ sourceText: string().nullable(),
23657
+ createdAt: string(),
23658
+ resolved: boolean().describe("For memory-kind targets: whether targetId points at a live memory you may read. Non-memory kinds (pr, agent-fs-file, …) are always resolved."),
23659
+ target: LinkedMemoryRefSchema.optional().describe("Linked memory metadata — present only for resolved memory-kind links.")
23660
+ });
23661
+ var MemoryBacklinkSchema = object({
23662
+ id: string(),
23663
+ linkType: string(),
23664
+ strength: number(),
23665
+ sourceText: string().nullable(),
23666
+ createdAt: string(),
23667
+ from: LinkedMemoryRefSchema.describe("The memory whose content links here.")
23668
+ });
23511
23669
  var registerMemoryGetTool = (server) => {
23512
23670
  createToolRegistrar(server)("memory-get", {
23513
23671
  title: "Get memory details",
@@ -23522,6 +23680,8 @@ var registerMemoryGetTool = (server) => {
23522
23680
  success: boolean(),
23523
23681
  message: string(),
23524
23682
  memory: AgentMemorySchema.optional(),
23683
+ links: array(MemoryLinkSchema).optional().describe("Outgoing memory_link rows resolved from this memory's content."),
23684
+ backlinks: array(MemoryBacklinkSchema).optional().describe("Other memories whose content links to this one (ACL-filtered)."),
23525
23685
  rateHint: string().optional()
23526
23686
  })
23527
23687
  }, async ({ memoryId, intent }, requestInfo, _meta) => {
@@ -23557,13 +23717,26 @@ var registerMemoryGetTool = (server) => {
23557
23717
  }
23558
23718
  const inTaskContext = !!requestInfo.sourceTaskId;
23559
23719
  const rateHint = inTaskContext && NUDGE_ELIGIBLE_SOURCES.has(memory.source) ? `memory_rate(id="${memory.id}", useful=true|false)` : undefined;
23720
+ let linkBlocks = { links: [], backlinks: [] };
23721
+ try {
23722
+ const agent = requestInfo.agentId ? getAgentById(requestInfo.agentId) : undefined;
23723
+ linkBlocks = getLinksForMemory(memory.id, {
23724
+ viewerAgentId: requestInfo.agentId,
23725
+ isLead: agent?.isLead ?? false
23726
+ });
23727
+ } catch (err) {
23728
+ console.error("[memory-get] link traversal failed:", err.message);
23729
+ }
23730
+ const linksSummary = linkBlocks.links.length > 0 || linkBlocks.backlinks.length > 0 ? `
23731
+
23732
+ [${linkBlocks.links.length} outgoing link(s), ${linkBlocks.backlinks.length} backlink(s) — see structured output]` : "";
23560
23733
  return {
23561
23734
  content: [
23562
23735
  {
23563
23736
  type: "text",
23564
23737
  text: `Memory "${memory.name}" retrieved.
23565
23738
 
23566
- ${memory.content}`
23739
+ ${memory.content}${linksSummary}`
23567
23740
  }
23568
23741
  ],
23569
23742
  structuredContent: {
@@ -23571,6 +23744,8 @@ ${memory.content}`
23571
23744
  success: true,
23572
23745
  message: `Memory "${memory.name}" retrieved.`,
23573
23746
  memory,
23747
+ links: linkBlocks.links,
23748
+ backlinks: linkBlocks.backlinks,
23574
23749
  rateHint
23575
23750
  }
23576
23751
  };
@@ -23688,6 +23863,103 @@ var registerMemoryRateTool = (server) => {
23688
23863
  init_zod();
23689
23864
  init_db();
23690
23865
  init_constants();
23866
+
23867
+ // src/be/memory/graph-expansion.ts
23868
+ init_db();
23869
+ init_constants();
23870
+ init_sqlite_store();
23871
+ init_reranker();
23872
+ function addNeighborScopeConditions(conditions, params, agentId, scope, isLead) {
23873
+ if (!isLead) {
23874
+ if (scope === "agent") {
23875
+ conditions.push("m.agentId = ? AND m.scope = 'agent'");
23876
+ params.push(agentId);
23877
+ } else if (scope === "swarm") {
23878
+ conditions.push("m.scope = 'swarm'");
23879
+ } else {
23880
+ conditions.push("(m.agentId = ? OR m.scope = 'swarm')");
23881
+ params.push(agentId);
23882
+ }
23883
+ } else {
23884
+ if (scope === "agent") {
23885
+ conditions.push("m.scope = 'agent'");
23886
+ } else if (scope === "swarm") {
23887
+ conditions.push("m.scope = 'swarm'");
23888
+ }
23889
+ }
23890
+ }
23891
+ function expandCandidatesWithGraph(candidates, agentId, options = {}) {
23892
+ if (!isGraphExpansionEnabled())
23893
+ return candidates;
23894
+ const { cap = 5, damping = 0.7, scope = "all", source, isLead = false } = options;
23895
+ if (candidates.length === 0 || cap <= 0)
23896
+ return candidates;
23897
+ const parentById = new Map(candidates.map((c) => [c.id, c]));
23898
+ const parentIds = [...parentById.keys()];
23899
+ const conditions = [
23900
+ `ml.from_memory_id IN (${parentIds.map(() => "?").join(", ")})`,
23901
+ "ml.targetKind = 'memory'",
23902
+ "(m.expiresAt IS NULL OR m.expiresAt > datetime('now'))"
23903
+ ];
23904
+ const params = [...parentIds];
23905
+ addNeighborScopeConditions(conditions, params, agentId, scope, isLead);
23906
+ if (source) {
23907
+ conditions.push("m.source = ?");
23908
+ params.push(source);
23909
+ }
23910
+ let rows;
23911
+ try {
23912
+ rows = getDb().prepare(`SELECT m.*, ml.from_memory_id AS fromMemoryId, ml.strength AS linkStrength
23913
+ FROM memory_link ml
23914
+ JOIN agent_memory m ON m.id = ml.targetId
23915
+ WHERE ${conditions.join(" AND ")}`).all(...params);
23916
+ } catch (err) {
23917
+ console.warn("[memory-graph] expansion query failed:", err.message);
23918
+ return candidates;
23919
+ }
23920
+ if (rows.length === 0)
23921
+ return candidates;
23922
+ const neighbors = new Map;
23923
+ for (const row of rows) {
23924
+ const parent = parentById.get(row.fromMemoryId);
23925
+ if (!parent)
23926
+ continue;
23927
+ const strength = typeof row.linkStrength === "number" ? row.linkStrength : 1;
23928
+ const parentBase = parent.rawSimilarity ?? parent.similarity;
23929
+ const similarity = parentBase * strength * damping;
23930
+ const existing = neighbors.get(row.id);
23931
+ if (existing && existing.similarity >= similarity)
23932
+ continue;
23933
+ neighbors.set(row.id, {
23934
+ ...rowToCandidate(row, similarity),
23935
+ retrievalSource: "graph",
23936
+ recencyDecayApplied: false
23937
+ });
23938
+ }
23939
+ if (neighbors.size === 0)
23940
+ return candidates;
23941
+ const result = [...candidates];
23942
+ const indexById = new Map(result.map((c, i) => [c.id, i]));
23943
+ let added = 0;
23944
+ const now = new Date;
23945
+ const ranked = [...neighbors.values()].sort((a, b) => computeScore(b, now) - computeScore(a, now));
23946
+ for (const neighbor of ranked) {
23947
+ const existingIndex = indexById.get(neighbor.id);
23948
+ if (existingIndex !== undefined) {
23949
+ if (computeScore(neighbor, now) > computeScore(result[existingIndex], now)) {
23950
+ result[existingIndex] = neighbor;
23951
+ }
23952
+ continue;
23953
+ }
23954
+ if (added >= cap)
23955
+ continue;
23956
+ result.push(neighbor);
23957
+ added += 1;
23958
+ }
23959
+ return result;
23960
+ }
23961
+
23962
+ // src/tools/memory-search.ts
23691
23963
  init_reranker();
23692
23964
  init_types();
23693
23965
  var NUDGE_ELIGIBLE_SOURCES2 = new Set(["manual", "file_index"]);
@@ -23717,7 +23989,8 @@ var registerMemorySearchTool = (server) => {
23717
23989
  source: AgentMemorySourceSchema,
23718
23990
  scope: AgentMemoryScopeSchema,
23719
23991
  similarity: number().optional(),
23720
- retrievalSource: _enum(["vec", "fts", "hybrid", "fallback"]).optional(),
23992
+ retrievalSource: _enum(["vec", "fts", "hybrid", "fallback", "graph"]).optional(),
23993
+ tags: array(string()).optional(),
23721
23994
  createdAt: string(),
23722
23995
  rateHint: string().optional()
23723
23996
  })).optional(),
@@ -23747,8 +24020,13 @@ var registerMemorySearchTool = (server) => {
23747
24020
  isLead,
23748
24021
  queryText: query
23749
24022
  });
23750
- if (candidates.length > 0) {
23751
- const ranked = rerank(candidates, { limit });
24023
+ const expanded = expandCandidatesWithGraph(candidates, requestInfo.agentId, {
24024
+ scope,
24025
+ source,
24026
+ isLead
24027
+ });
24028
+ if (expanded.length > 0) {
24029
+ const ranked = rerank(expanded, { limit });
23752
24030
  if (requestInfo.sourceTaskId) {
23753
24031
  try {
23754
24032
  recordRetrievals(requestInfo.sourceTaskId, requestInfo.agentId, ranked.map((r) => ({
@@ -23769,6 +24047,7 @@ var registerMemorySearchTool = (server) => {
23769
24047
  scope: r.scope,
23770
24048
  similarity: r.similarity,
23771
24049
  retrievalSource: r.retrievalSource,
24050
+ tags: r.tags,
23772
24051
  createdAt: r.createdAt,
23773
24052
  ...inTaskContext && NUDGE_ELIGIBLE_SOURCES2.has(r.source) ? { rateHint: rateHintFor(r.id) } : {}
23774
24053
  }));
@@ -23802,6 +24081,7 @@ var registerMemorySearchTool = (server) => {
23802
24081
  summary: r.summary,
23803
24082
  source: r.source,
23804
24083
  scope: r.scope,
24084
+ tags: r.tags,
23805
24085
  createdAt: r.createdAt
23806
24086
  }));
23807
24087
  return {
@@ -28663,6 +28943,87 @@ var registerSkillUpdateTool = (server) => {
28663
28943
  }
28664
28944
  });
28665
28945
  };
28946
+ // src/tools/slack-delete.ts
28947
+ init_zod();
28948
+ init_db();
28949
+ var registerSlackDeleteTool = (server) => {
28950
+ createToolRegistrar(server)("slack-delete", {
28951
+ title: "Delete a Slack message",
28952
+ description: "Deletes a Slack message that THIS bot authored (e.g. a message previously posted via `slack-post`/`slack-reply`). Cannot delete messages authored by humans or other apps. Requires lead privileges.",
28953
+ annotations: { openWorldHint: true, destructiveHint: true },
28954
+ inputSchema: object({
28955
+ channelId: string().min(1).describe("The Slack channel ID the message is in."),
28956
+ messageTs: string().min(1).describe("Timestamp of the message to delete. Accepts the dotted form (1783411554.596189), the 'p' deep-link form (p1783411554596189), or a full Slack permalink URL.")
28957
+ }),
28958
+ outputSchema: object({
28959
+ success: boolean(),
28960
+ message: string()
28961
+ })
28962
+ }, async ({ channelId, messageTs }, requestInfo, _meta) => {
28963
+ if (!requestInfo.agentId) {
28964
+ return {
28965
+ content: [{ type: "text", text: "Agent ID not found." }],
28966
+ structuredContent: { success: false, message: "Agent ID not found." }
28967
+ };
28968
+ }
28969
+ const agent = getAgentById(requestInfo.agentId);
28970
+ if (!agent) {
28971
+ return {
28972
+ content: [{ type: "text", text: "Agent not found." }],
28973
+ structuredContent: { success: false, message: "Agent not found." }
28974
+ };
28975
+ }
28976
+ if (!agent.isLead) {
28977
+ return {
28978
+ content: [{ type: "text", text: "Deleting Slack messages requires lead privileges." }],
28979
+ structuredContent: {
28980
+ success: false,
28981
+ message: "Deleting Slack messages requires lead privileges."
28982
+ }
28983
+ };
28984
+ }
28985
+ const app = getSlackApp();
28986
+ if (!app) {
28987
+ return {
28988
+ content: [{ type: "text", text: "Slack not configured." }],
28989
+ structuredContent: { success: false, message: "Slack not configured." }
28990
+ };
28991
+ }
28992
+ try {
28993
+ const ts = parseSlackTs(messageTs);
28994
+ await app.client.chat.delete({ channel: channelId, ts });
28995
+ return {
28996
+ content: [{ type: "text", text: "Message deleted successfully." }],
28997
+ structuredContent: { success: true, message: "Message deleted successfully." }
28998
+ };
28999
+ } catch (error) {
29000
+ const errorCode = error?.data?.error;
29001
+ const errorMsg = error instanceof Error ? error.message : String(error);
29002
+ let message;
29003
+ switch (errorCode) {
29004
+ case "message_not_found":
29005
+ message = "No message found at that timestamp in this channel.";
29006
+ break;
29007
+ case "cant_delete_message":
29008
+ message = "Cannot delete this message — the bot can only delete messages it authored.";
29009
+ break;
29010
+ case "channel_not_found":
29011
+ message = "Channel not found or the bot has no access.";
29012
+ break;
29013
+ case "not_in_channel":
29014
+ message = "The bot is not in that channel.";
29015
+ break;
29016
+ default:
29017
+ message = `Failed to delete message: ${errorMsg}`;
29018
+ }
29019
+ return {
29020
+ content: [{ type: "text", text: message }],
29021
+ structuredContent: { success: false, message }
29022
+ };
29023
+ }
29024
+ });
29025
+ };
29026
+
28666
29027
  // src/tools/slack-download-file.ts
28667
29028
  init_zod();
28668
29029
  init_db();
@@ -29476,7 +29837,8 @@ var registerSlackReplyTool = (server) => {
29476
29837
  }),
29477
29838
  outputSchema: object({
29478
29839
  success: boolean(),
29479
- message: string()
29840
+ message: string(),
29841
+ messageTs: string().optional()
29480
29842
  })
29481
29843
  }, async ({ inboxMessageId, taskId, message }, requestInfo, _meta) => {
29482
29844
  if (!requestInfo.agentId) {
@@ -29548,7 +29910,7 @@ var registerSlackReplyTool = (server) => {
29548
29910
  }
29549
29911
  try {
29550
29912
  const slackMessage = markdownToSlack(message);
29551
- await withAutoJoin(app.client, slackChannelId, () => app.client.chat.postMessage({
29913
+ const result = await withAutoJoin(app.client, slackChannelId, () => app.client.chat.postMessage({
29552
29914
  channel: slackChannelId,
29553
29915
  thread_ts: slackThreadTs,
29554
29916
  text: slackMessage,
@@ -29564,13 +29926,19 @@ var registerSlackReplyTool = (server) => {
29564
29926
  }
29565
29927
  ]
29566
29928
  }));
29929
+ const messageTs = result.ts;
29567
29930
  if (taskId) {
29568
29931
  markTaskSlackReplySent(taskId);
29569
29932
  console.log(`[Slack] Marked slackReplySent=1 for task ${taskId}`);
29570
29933
  }
29571
29934
  return {
29572
- content: [{ type: "text", text: "Reply sent successfully." }],
29573
- structuredContent: { success: true, message: "Reply sent successfully." }
29935
+ content: [
29936
+ {
29937
+ type: "text",
29938
+ text: `Reply sent successfully.${messageTs ? ` Message timestamp: ${messageTs}` : ""}`
29939
+ }
29940
+ ],
29941
+ structuredContent: { success: true, message: "Reply sent successfully.", messageTs }
29574
29942
  };
29575
29943
  } catch (error) {
29576
29944
  return {
@@ -29597,7 +29965,8 @@ var registerSlackStartThreadTool = (server) => {
29597
29965
  success: boolean(),
29598
29966
  message: string(),
29599
29967
  channelId: string().optional(),
29600
- ts: string().optional()
29968
+ ts: string().optional(),
29969
+ messageTs: string().optional()
29601
29970
  })
29602
29971
  }, async ({ channelId, message }, requestInfo, _meta) => {
29603
29972
  if (!requestInfo.agentId) {
@@ -29674,7 +30043,8 @@ var registerSlackStartThreadTool = (server) => {
29674
30043
  success: true,
29675
30044
  message: "Thread started successfully.",
29676
30045
  channelId: resolvedChannelId,
29677
- ts
30046
+ ts,
30047
+ messageTs: ts
29678
30048
  }
29679
30049
  };
29680
30050
  } catch (error) {
@@ -29687,10 +30057,122 @@ var registerSlackStartThreadTool = (server) => {
29687
30057
  });
29688
30058
  };
29689
30059
 
30060
+ // src/tools/slack-update.ts
30061
+ init_zod();
30062
+ init_db();
30063
+ var registerSlackUpdateTool = (server) => {
30064
+ createToolRegistrar(server)("slack-update", {
30065
+ title: "Edit a Slack message",
30066
+ description: "Edits (in place) the text of a Slack message that THIS bot authored — use it to post corrections to your own messages. Cannot edit messages authored by humans or other apps. Note: editing may reset the message's display name/icon to the app default (Slack's chat.update cannot set the crown persona). Requires lead privileges.",
30067
+ annotations: { openWorldHint: true },
30068
+ inputSchema: object({
30069
+ channelId: string().min(1).describe("The Slack channel ID the message is in."),
30070
+ messageTs: string().min(1).describe("Timestamp of the message to edit (dotted, 'p' deep-link, or full permalink URL)."),
30071
+ message: string().min(1).max(4000).describe("The new message content.")
30072
+ }),
30073
+ outputSchema: object({
30074
+ success: boolean(),
30075
+ message: string(),
30076
+ messageTs: string().optional()
30077
+ })
30078
+ }, async ({ channelId, messageTs, message }, requestInfo, _meta) => {
30079
+ if (!requestInfo.agentId) {
30080
+ return {
30081
+ content: [{ type: "text", text: "Agent ID not found." }],
30082
+ structuredContent: { success: false, message: "Agent ID not found." }
30083
+ };
30084
+ }
30085
+ const agent = getAgentById(requestInfo.agentId);
30086
+ if (!agent) {
30087
+ return {
30088
+ content: [{ type: "text", text: "Agent not found." }],
30089
+ structuredContent: { success: false, message: "Agent not found." }
30090
+ };
30091
+ }
30092
+ if (!agent.isLead) {
30093
+ return {
30094
+ content: [{ type: "text", text: "Editing Slack messages requires lead privileges." }],
30095
+ structuredContent: {
30096
+ success: false,
30097
+ message: "Editing Slack messages requires lead privileges."
30098
+ }
30099
+ };
30100
+ }
30101
+ const app = getSlackApp();
30102
+ if (!app) {
30103
+ return {
30104
+ content: [{ type: "text", text: "Slack not configured." }],
30105
+ structuredContent: { success: false, message: "Slack not configured." }
30106
+ };
30107
+ }
30108
+ try {
30109
+ const ts = parseSlackTs(messageTs);
30110
+ const slackMessage = markdownToSlack(message);
30111
+ const result = await app.client.chat.update({
30112
+ channel: channelId,
30113
+ ts,
30114
+ text: slackMessage,
30115
+ blocks: [
30116
+ {
30117
+ type: "section",
30118
+ text: {
30119
+ type: "mrkdwn",
30120
+ text: slackMessage
30121
+ }
30122
+ }
30123
+ ]
30124
+ });
30125
+ return {
30126
+ content: [{ type: "text", text: "Message updated successfully." }],
30127
+ structuredContent: {
30128
+ success: true,
30129
+ message: "Message updated successfully.",
30130
+ messageTs: result.ts
30131
+ }
30132
+ };
30133
+ } catch (error) {
30134
+ const errorCode = error?.data?.error;
30135
+ const errorMsg = error instanceof Error ? error.message : String(error);
30136
+ let message2;
30137
+ switch (errorCode) {
30138
+ case "message_not_found":
30139
+ message2 = "No message found at that timestamp in this channel.";
30140
+ break;
30141
+ case "cant_update_message":
30142
+ message2 = "Cannot edit this message — the bot can only edit messages it authored.";
30143
+ break;
30144
+ case "edit_window_closed":
30145
+ message2 = "The edit window for this message has closed.";
30146
+ break;
30147
+ case "channel_not_found":
30148
+ message2 = "Channel not found or the bot has no access.";
30149
+ break;
30150
+ case "not_in_channel":
30151
+ message2 = "The bot is not in that channel.";
30152
+ break;
30153
+ default:
30154
+ message2 = `Failed to update message: ${errorMsg}`;
30155
+ }
30156
+ return {
30157
+ content: [{ type: "text", text: message2 }],
30158
+ structuredContent: { success: false, message: message2 }
30159
+ };
30160
+ }
30161
+ });
30162
+ };
30163
+
29690
30164
  // src/tools/slack-upload-file.ts
29691
30165
  init_zod();
29692
30166
  init_db();
29693
30167
  var WORKSPACE_DIR = "/workspace";
30168
+ function resolveTaskUploadThreadTs(task) {
30169
+ if (!task)
30170
+ return;
30171
+ if (task.slackChannelId?.startsWith("D")) {
30172
+ return task.slackTreeRootMessageTs ?? task.slackProgressMessageTs ?? task.slackThreadTs;
30173
+ }
30174
+ return task.slackThreadTs;
30175
+ }
29694
30176
  async function resolveAndValidateFilePath(filePath) {
29695
30177
  const triedPaths = [];
29696
30178
  const fileExists = async (path2) => {
@@ -29798,7 +30280,7 @@ var registerSlackUploadFileTool = (server) => {
29798
30280
  };
29799
30281
  }
29800
30282
  slackChannelId = task.slackChannelId;
29801
- slackThreadTs = task.slackThreadTs;
30283
+ slackThreadTs = resolveTaskUploadThreadTs(task);
29802
30284
  } else if (channelId) {
29803
30285
  if (!agent.isLead) {
29804
30286
  return {
@@ -32021,24 +32503,25 @@ function parseNextPageLink(linkHeader) {
32021
32503
  const match = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
32022
32504
  return match ? match[1] ?? null : null;
32023
32505
  }
32024
- async function fetchReviewComments(repo, prNumber, reviewId, installationId) {
32025
- const token = await getInstallationToken(installationId);
32026
- if (!token) {
32027
- return [];
32028
- }
32029
- const headers = {
32506
+ var REVIEW_COMMENTS_EMPTY_RETRY_DELAYS_MS = [1500, 3000, 3000];
32507
+ var defaultReviewCommentsRetryDelay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
32508
+ var reviewCommentsRetryDelay = defaultReviewCommentsRetryDelay;
32509
+ function buildReviewCommentsHeaders(token) {
32510
+ return {
32030
32511
  Accept: "application/vnd.github+json",
32031
32512
  Authorization: `Bearer ${token}`,
32032
32513
  "X-GitHub-Api-Version": "2022-11-28"
32033
32514
  };
32515
+ }
32516
+ async function fetchPaginatedReviewComments(initialUrl, headers) {
32034
32517
  const allComments = [];
32035
- let url = `https://api.github.com/repos/${repo}/pulls/${prNumber}/reviews/${reviewId}/comments?per_page=100`;
32518
+ let url = initialUrl;
32036
32519
  try {
32037
32520
  while (url) {
32038
32521
  const response = await fetch(url, { headers });
32039
32522
  if (!response.ok) {
32040
32523
  console.error(`[GitHub] Failed to fetch review inline comments: ${response.status}`);
32041
- return allComments;
32524
+ return { comments: allComments, degraded: true };
32042
32525
  }
32043
32526
  const page = await response.json();
32044
32527
  if (Array.isArray(page)) {
@@ -32046,11 +32529,66 @@ async function fetchReviewComments(repo, prNumber, reviewId, installationId) {
32046
32529
  }
32047
32530
  url = parseNextPageLink(response.headers.get("link"));
32048
32531
  }
32049
- return allComments;
32532
+ return { comments: allComments, degraded: false };
32050
32533
  } catch (error) {
32051
32534
  console.error("[GitHub] Error fetching review inline comments:", error);
32052
- return allComments;
32535
+ return { comments: allComments, degraded: true };
32536
+ }
32537
+ }
32538
+ async function fetchReviewScopedComments(repo, prNumber, reviewId, headers) {
32539
+ const url = `https://api.github.com/repos/${repo}/pulls/${prNumber}/reviews/${reviewId}/comments?per_page=100`;
32540
+ let result = await fetchPaginatedReviewComments(url, headers);
32541
+ if (result.degraded || result.comments.length > 0) {
32542
+ return result;
32543
+ }
32544
+ for (const delayMs of REVIEW_COMMENTS_EMPTY_RETRY_DELAYS_MS) {
32545
+ await reviewCommentsRetryDelay(delayMs);
32546
+ result = await fetchPaginatedReviewComments(url, headers);
32547
+ if (result.degraded || result.comments.length > 0) {
32548
+ return result;
32549
+ }
32550
+ }
32551
+ return result;
32552
+ }
32553
+ async function fetchPrLevelReviewComments(repo, prNumber, reviewId, headers) {
32554
+ const url = `https://api.github.com/repos/${repo}/pulls/${prNumber}/comments?per_page=100`;
32555
+ const result = await fetchPaginatedReviewComments(url, headers);
32556
+ return {
32557
+ comments: result.comments.filter((comment) => comment.pull_request_review_id === reviewId),
32558
+ degraded: result.degraded
32559
+ };
32560
+ }
32561
+ function dedupeReviewComments(comments) {
32562
+ const byId = new Map;
32563
+ for (const comment of comments) {
32564
+ if (!byId.has(comment.id)) {
32565
+ byId.set(comment.id, comment);
32566
+ }
32053
32567
  }
32568
+ return [...byId.values()];
32569
+ }
32570
+ async function fetchReviewComments(repo, prNumber, reviewId, installationId) {
32571
+ const token = await getInstallationToken(installationId);
32572
+ if (!token) {
32573
+ return { comments: [], degraded: true };
32574
+ }
32575
+ const headers = buildReviewCommentsHeaders(token);
32576
+ const scopedResult = await fetchReviewScopedComments(repo, prNumber, reviewId, headers);
32577
+ if (!scopedResult.degraded && scopedResult.comments.length > 0) {
32578
+ return scopedResult;
32579
+ }
32580
+ const fallbackResult = await fetchPrLevelReviewComments(repo, prNumber, reviewId, headers);
32581
+ const mergedComments = dedupeReviewComments([
32582
+ ...scopedResult.comments,
32583
+ ...fallbackResult.comments
32584
+ ]);
32585
+ if (fallbackResult.comments.length > 0) {
32586
+ return { comments: mergedComments, degraded: false };
32587
+ }
32588
+ return {
32589
+ comments: mergedComments,
32590
+ degraded: scopedResult.degraded || fallbackResult.degraded
32591
+ };
32054
32592
  }
32055
32593
  function buildInlineCommentsSection(comments) {
32056
32594
  if (comments.length === 0)
@@ -32072,6 +32610,14 @@ ${items.join(`
32072
32610
 
32073
32611
  `)}`;
32074
32612
  }
32613
+ function buildInlineCommentsDegradedSection(repo, prNumber) {
32614
+ return `
32615
+
32616
+ ## ⚠️ Inline comments could NOT be auto-fetched
32617
+ The automatic inline-comment fetch failed or was unverifiable while the reviewer submitted this review. Inline comments ARE the change requests. BEFORE scoping or dispatching this task you MUST fetch them yourself:
32618
+ \`gh api "repos/${repo}/pulls/${prNumber}/comments?per_page=100" --jq '.[] | {id,path,line,body}'\`
32619
+ Reply to and resolve EVERY unresolved inline thread. Do NOT dispatch off the review body alone.`;
32620
+ }
32075
32621
  async function handlePullRequestReview(event) {
32076
32622
  const { action, review, pull_request: pr, repository, sender, installation } = event;
32077
32623
  const requestedByUserId = resolveGitHubSender(sender.login, "pull_request_review", `Review on PR #${pr.number}: ${review.state}`);
@@ -32082,8 +32628,8 @@ async function handlePullRequestReview(event) {
32082
32628
  if (isDuplicate2(eventKey)) {
32083
32629
  return { created: false };
32084
32630
  }
32085
- const inlineComments = installation?.id ? await fetchReviewComments(repository.full_name, pr.number, review.id, installation.id) : [];
32086
- if (review.state === "commented" && !review.body && inlineComments.length === 0) {
32631
+ const { comments: inlineComments, degraded } = installation?.id ? await fetchReviewComments(repository.full_name, pr.number, review.id, installation.id) : { comments: [], degraded: true };
32632
+ if (review.state === "commented" && !review.body && inlineComments.length === 0 && !degraded) {
32087
32633
  return { created: false };
32088
32634
  }
32089
32635
  const existingTask = findTaskByVcs(repository.full_name, pr.number);
@@ -32097,13 +32643,13 @@ async function handlePullRequestReview(event) {
32097
32643
 
32098
32644
  Review Comment:
32099
32645
  ${review.body}` : "";
32100
- const inlineCommentsSection = buildInlineCommentsSection(inlineComments);
32646
+ const inlineCommentsSection = buildInlineCommentsSection(inlineComments) + (degraded ? buildInlineCommentsDegradedSection(repository.full_name, pr.number) : "");
32101
32647
  const relatedTaskSection = existingTask ? `Related task: ${existingTask.id}
32102
32648
  \uD83D\uDD00 Consider routing to the same agent working on the related task.
32103
32649
  ` : "";
32104
32650
  const hasInlineComments = inlineComments.length > 0;
32105
32651
  const baseReviewSuggestion = review.state === "approved" ? "\uD83D\uDCA1 Suggested: Merge the PR or wait for additional reviews" : review.state === "changes_requested" ? "\uD83D\uDCA1 Suggested: Address the requested changes and update the PR" : "\uD83D\uDCA1 Suggested: Review the feedback and respond if needed";
32106
- const reviewSuggestions = hasInlineComments ? `${baseReviewSuggestion}
32652
+ const reviewSuggestions = hasInlineComments || degraded ? `${baseReviewSuggestion}
32107
32653
  \uD83D\uDCAC Address EVERY inline comment. After pushing fixes, reply to and resolve each inline review thread on GitHub so the reviewer sees visible confirmation.` : baseReviewSuggestion;
32108
32654
  const result = resolveTemplate("github.pull_request.review_submitted", {
32109
32655
  review_emoji: emoji,
@@ -32884,7 +33430,7 @@ async function notifySlack(text) {
32884
33430
  return;
32885
33431
  }
32886
33432
  try {
32887
- const { getSlackApp: getSlackApp2 } = await import("./app-85h97pg2.js");
33433
+ const { getSlackApp: getSlackApp2 } = await import("./app-b05sgm02.js");
32888
33434
  const app = getSlackApp2();
32889
33435
  if (!app) {
32890
33436
  console.warn("[Jira webhook keepalive] Slack not available, cannot send notification");
@@ -32953,7 +33499,7 @@ async function deleteJiraWebhook(webhookId) {
32953
33499
  console.log(`[Jira webhook keepalive] Deleted webhook id=${webhookId}`);
32954
33500
  }
32955
33501
  async function overwriteWebhookIds(next) {
32956
- const { getDb: getDb2 } = await import("./db-kwfxkhpw.js");
33502
+ const { getDb: getDb2 } = await import("./db-3tge4jrn.js");
32957
33503
  const db = getDb2();
32958
33504
  const txn = db.transaction(() => {
32959
33505
  const row = db.query("SELECT metadata FROM oauth_apps WHERE provider = 'jira'").get();
@@ -110005,6 +110551,8 @@ function createServer() {
110005
110551
  registerSlackListChannelsTool(server);
110006
110552
  registerSlackUploadFileTool(server);
110007
110553
  registerSlackDownloadFileTool(server);
110554
+ registerSlackDeleteTool(server);
110555
+ registerSlackUpdateTool(server);
110008
110556
  registerRegisterAgentmailInboxTool(server);
110009
110557
  registerRegisterKapsoNumberTool(server);
110010
110558
  registerUnregisterKapsoNumberTool(server);
@@ -110240,4 +110788,4 @@ function writeSkillsToFilesystem(entries, harnessType = "all", home) {
110240
110788
  return { synced, removed, errors };
110241
110789
  }
110242
110790
 
110243
- export { DEFAULT_NEGOTIATED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, isJSONRPCRequest, isJSONRPCResultResponse, isJSONRPCErrorResponse, JSONRPCMessageSchema, isInitializeRequest, McpServer, isGitHubEnabled, initGitHub, verifyWebhookSignature, userCtx, isPollTracingEnabled, initOtel, withSpan, startSpan, withSpanContext, withRemoteContext, injectTraceContext, recordSessionCost, createToolRegistrar, cancelTaskInputSchema, cancelTaskOutputSchema, cancelTaskHandler, setCorsHeaders, parseQueryParams, getPathSegments, safeRequestUrlForLog, agentWithCapacity, parseBody, BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError, triggerSchemaErrorResponse, deriveApiBaseUrl, httpServerSemconvAttributes, describeRequestRoute, route, assertSelectOnlyQuery, executeReadOnlyQuery, handleDbQuery, snapshotMetric, snapshotPage, getTaskDetailsInputSchema, getTaskDetailsOutputSchema, getTaskDetailsHandler, getTasksInputSchema, getTasksOutputSchema, getTasksHandler, storeLinks, canReadMemory, recordRetrievals, markKapsoMessageRead, sendKapsoReaction, getKapsoConfig, getKapsoNumberMapping, markKapsoMessageSeen, resolveHttpAuditUserId, createEvent, createEventsBatch, getEventCountsFiltered, getEventsFiltered, mergeScheduleTiming, validateRecurringTiming, sendTaskOutputSchema, sendTaskHandler, parseSkillContent, ExplicitSelfDuplicateError, applyRating, shouldPersistAutomaticTaskMemory, isAgentMailEnabled, initAgentMail, verifyAgentMailWebhook, isInboxAllowed, isSenderAllowed, handleMessageReceived, handlePullRequest, handleIssue, handleComment, handlePullRequestReview, handleCheckRun, handleCheckSuite, handleWorkflowRun, getJiraMetadata, updateJiraMetadata, clearJiraMetadata, registerJiraWebhook, deleteJiraWebhook, isJiraEnabled, initJira, handleJiraWebhook, handleAgentSessionEvent, handleIssueUpdate, handleIssueDelete, handleAgentSessionPrompted, isLinearEnabled, initLinear, loadGlobalConfigsIntoEnv, reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload, handleCore, canClaim, emitBudgetRefusalSideEffects, taskActionInputSchema, taskActionOutputSchema, taskActionHandler, hasCapability, getEnabledCapabilities, createServer, writeSkillsToFilesystem };
110791
+ export { DEFAULT_NEGOTIATED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, isJSONRPCRequest, isJSONRPCResultResponse, isJSONRPCErrorResponse, JSONRPCMessageSchema, isInitializeRequest, McpServer, isGitHubEnabled, initGitHub, verifyWebhookSignature, userCtx, isPollTracingEnabled, initOtel, withSpan, startSpan, withSpanContext, withRemoteContext, injectTraceContext, recordSessionCost, createToolRegistrar, cancelTaskInputSchema, cancelTaskOutputSchema, cancelTaskHandler, setCorsHeaders, parseQueryParams, getPathSegments, safeRequestUrlForLog, agentWithCapacity, parseBody, BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError, triggerSchemaErrorResponse, deriveApiBaseUrl, httpServerSemconvAttributes, describeRequestRoute, route, assertSelectOnlyQuery, executeReadOnlyQuery, handleDbQuery, snapshotMetric, snapshotPage, getTaskDetailsInputSchema, getTaskDetailsOutputSchema, getTaskDetailsHandler, getTasksInputSchema, getTasksOutputSchema, getTasksHandler, storeLinks, refreshLinks, canReadMemory, getLinksForMemory, recordRetrievals, expandCandidatesWithGraph, markKapsoMessageRead, sendKapsoReaction, getKapsoConfig, getKapsoNumberMapping, markKapsoMessageSeen, resolveHttpAuditUserId, createEvent, createEventsBatch, getEventCountsFiltered, getEventsFiltered, mergeScheduleTiming, validateRecurringTiming, sendTaskOutputSchema, sendTaskHandler, parseSkillContent, ExplicitSelfDuplicateError, applyRating, shouldPersistAutomaticTaskMemory, isAgentMailEnabled, initAgentMail, verifyAgentMailWebhook, isInboxAllowed, isSenderAllowed, handleMessageReceived, handlePullRequest, handleIssue, handleComment, handlePullRequestReview, handleCheckRun, handleCheckSuite, handleWorkflowRun, getJiraMetadata, updateJiraMetadata, clearJiraMetadata, registerJiraWebhook, deleteJiraWebhook, isJiraEnabled, initJira, handleJiraWebhook, handleAgentSessionEvent, handleIssueUpdate, handleIssueDelete, handleAgentSessionPrompted, isLinearEnabled, initLinear, loadGlobalConfigsIntoEnv, reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload, handleCore, canClaim, emitBudgetRefusalSideEffects, taskActionInputSchema, taskActionOutputSchema, taskActionHandler, hasCapability, getEnabledCapabilities, createServer, writeSkillsToFilesystem };