@integrity-labs/agt-cli 0.28.319 → 0.28.321

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.
@@ -2,8 +2,10 @@ import {
2
2
  ApiError,
3
3
  CHANNEL_SECRET_ENV_KEYS,
4
4
  HostFlagStore,
5
+ HttpOpencodeClient,
5
6
  INTEGRATIONS_SECTION_END,
6
7
  INTEGRATIONS_SECTION_START,
8
+ OpencodeInboundBridge,
7
9
  SUPERVISOR_RESTART_EXIT_CODE,
8
10
  api,
9
11
  atomicWriteFileSync,
@@ -35,12 +37,13 @@ import {
35
37
  provisionPreCompactHook,
36
38
  provisionSessionStateHook,
37
39
  provisionStopHook,
40
+ readChannelServerEnv,
38
41
  reapMissingMcpSessions,
39
42
  reapStaleMcpChildren,
40
43
  requireHost,
41
44
  safeWriteJsonAtomic,
42
45
  setConfigHash
43
- } from "../chunk-373GH3RH.js";
46
+ } from "../chunk-52RHLSXN.js";
44
47
  import {
45
48
  getProjectDir as getProjectDir2,
46
49
  getReadyTasks,
@@ -129,17 +132,17 @@ import {
129
132
  takeZombieDetection,
130
133
  transcriptActivityAgeSeconds,
131
134
  writeEgressAllowlist
132
- } from "../chunk-ISHK77EM.js";
135
+ } from "../chunk-NAS4DZNG.js";
133
136
  import {
134
137
  reapOrphanChannelMcps
135
138
  } from "../chunk-XWVM4KPK.js";
136
139
 
137
140
  // src/lib/manager-worker.ts
138
- import { createHash as createHash11 } from "crypto";
139
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync10, rmSync as rmSync4, readdirSync as readdirSync5, statSync as statSync4, copyFileSync } from "fs";
141
+ import { createHash as createHash12 } from "crypto";
142
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync11, rmSync as rmSync4, readdirSync as readdirSync5, statSync as statSync4, copyFileSync } from "fs";
140
143
  import { execFileSync as syncExecFile } from "child_process";
141
- import { join as join19, dirname as dirname6 } from "path";
142
- import { homedir as homedir10 } from "os";
144
+ import { join as join20, dirname as dirname6 } from "path";
145
+ import { homedir as homedir11 } from "os";
143
146
  import { fileURLToPath } from "url";
144
147
 
145
148
  // src/lib/claude-code-upgrade-throttle.ts
@@ -515,6 +518,9 @@ function extractMsTeamsBehaviourSubset(config2) {
515
518
  adaptive_cards_ask_user_enabled: adaptiveCardsEnabled && config2?.["adaptive_cards_ask_user_enabled"] === true
516
519
  };
517
520
  }
521
+ function isMsTeamsBehaviourRestrictive(_subset) {
522
+ return false;
523
+ }
518
524
 
519
525
  // src/lib/slack-behaviour-restart.ts
520
526
  function extractSlackBehaviourSubset(config2) {
@@ -531,6 +537,9 @@ function extractSlackBehaviourSubset(config2) {
531
537
  allowed_users: Array.isArray(rawAllowed) ? rawAllowed.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : []
532
538
  };
533
539
  }
540
+ function isSlackBehaviourRestrictive(subset) {
541
+ return subset.allowed_users.length > 0;
542
+ }
534
543
 
535
544
  // src/lib/onboarding-drive.ts
536
545
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
@@ -3312,6 +3321,14 @@ import { existsSync as existsSync4, readFileSync as readFileSync11 } from "fs";
3312
3321
  import { join as join11 } from "path";
3313
3322
  var BASELINE_FILENAME = "sender-policy-baseline.json";
3314
3323
  var SENDER_POLICY_BASELINE_VERSION = 1;
3324
+ var BASELINE_CONCERNS = ["senderPolicy", "slackBehaviour", "msteamsBehaviour"];
3325
+ function createDeliveryBaselineMaps() {
3326
+ return {
3327
+ senderPolicy: /* @__PURE__ */ new Map(),
3328
+ slackBehaviour: /* @__PURE__ */ new Map(),
3329
+ msteamsBehaviour: /* @__PURE__ */ new Map()
3330
+ };
3331
+ }
3315
3332
  function getSenderPolicyBaselineFile(configDir) {
3316
3333
  return join11(configDir, BASELINE_FILENAME);
3317
3334
  }
@@ -3340,27 +3357,31 @@ function loadSenderPolicyBaseline(target, configDir, log2) {
3340
3357
  );
3341
3358
  return;
3342
3359
  }
3343
- const entries = obj.senderPolicy;
3344
- if (!entries || typeof entries !== "object" || Array.isArray(entries)) return;
3345
- for (const [agentId, hash] of Object.entries(entries)) {
3346
- if (typeof hash === "string" && hash.length > 0) target.set(agentId, hash);
3360
+ for (const concern of BASELINE_CONCERNS) {
3361
+ const entries = obj[concern];
3362
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) continue;
3363
+ for (const [agentId, hash] of Object.entries(entries)) {
3364
+ if (typeof hash === "string" && hash.length > 0) target[concern].set(agentId, hash);
3365
+ }
3347
3366
  }
3348
3367
  }
3349
3368
  function saveSenderPolicyBaseline(source, configDir, log2) {
3350
3369
  const path = getSenderPolicyBaselineFile(configDir);
3351
- const senderPolicy = {};
3352
- for (const [agentId, hash] of source) senderPolicy[agentId] = hash;
3353
- const payload = JSON.stringify(
3354
- { version: SENDER_POLICY_BASELINE_VERSION, senderPolicy },
3355
- null,
3356
- 2
3357
- );
3370
+ const payloadObj = { version: SENDER_POLICY_BASELINE_VERSION };
3371
+ for (const concern of BASELINE_CONCERNS) {
3372
+ const entries = {};
3373
+ for (const [agentId, hash] of source[concern]) entries[agentId] = hash;
3374
+ payloadObj[concern] = entries;
3375
+ }
3376
+ const payload = JSON.stringify(payloadObj, null, 2);
3358
3377
  try {
3359
3378
  atomicWriteFileSync(path, payload);
3379
+ return true;
3360
3380
  } catch (err) {
3361
3381
  log2?.(
3362
3382
  `[sender-policy] failed to persist ${BASELINE_FILENAME} (non-fatal): ${err.message}`
3363
3383
  );
3384
+ return false;
3364
3385
  }
3365
3386
  }
3366
3387
 
@@ -5438,6 +5459,1103 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
5438
5459
  }
5439
5460
  }
5440
5461
 
5462
+ // src/lib/opencode-session.ts
5463
+ import { spawn, execSync } from "child_process";
5464
+ import { createServer } from "net";
5465
+ import { randomBytes } from "crypto";
5466
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync15 } from "fs";
5467
+ import { homedir as homedir9, userInfo } from "os";
5468
+ import { join as join18 } from "path";
5469
+ var OPENCODE_BIN = process.env["AGT_OPENCODE_BIN"]?.trim() || "opencode";
5470
+ var sessions = /* @__PURE__ */ new Map();
5471
+ var bridges = /* @__PURE__ */ new Map();
5472
+ function opencodeTmuxSession(codeName) {
5473
+ return `agt-oc-${codeName}`;
5474
+ }
5475
+ function opencodePaneLogPath(codeName) {
5476
+ return join18(homedir9(), ".augmented", codeName, "opencode-serve.log");
5477
+ }
5478
+ function baseUrlFor(port) {
5479
+ return `http://127.0.0.1:${port}`;
5480
+ }
5481
+ function findFreePort() {
5482
+ return new Promise((resolve, reject) => {
5483
+ const srv = createServer();
5484
+ srv.on("error", reject);
5485
+ srv.listen(0, "127.0.0.1", () => {
5486
+ const addr = srv.address();
5487
+ if (addr && typeof addr === "object") {
5488
+ const { port } = addr;
5489
+ srv.close(() => resolve(port));
5490
+ } else {
5491
+ srv.close(() => reject(new Error("could not allocate a port")));
5492
+ }
5493
+ });
5494
+ });
5495
+ }
5496
+ async function startOpencodeSession(config2) {
5497
+ const { codeName, log: log2 } = config2;
5498
+ const existing = sessions.get(codeName);
5499
+ if (existing && existing.status === "running") return existing;
5500
+ const restartCount = existing?.restartCount ?? 0;
5501
+ if (existing?.status === "crashed" && existing.startedAt) {
5502
+ const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
5503
+ if (Date.now() - existing.startedAt < backoffMs) return existing;
5504
+ }
5505
+ if (config2.isolated) {
5506
+ log2(
5507
+ `[opencode-session] refusing to spawn '${codeName}': Docker isolation requested but the opencode container runtime is not implemented yet (ADR-0047). Not running unisolated on a multi-tenant host.`
5508
+ );
5509
+ const blocked = {
5510
+ codeName,
5511
+ startedAt: Date.now(),
5512
+ restartCount: restartCount + 1,
5513
+ status: "crashed",
5514
+ port: null,
5515
+ password: null,
5516
+ lastFailureTail: "opencode isolation not implemented (ADR-0047)"
5517
+ };
5518
+ sessions.set(codeName, blocked);
5519
+ return blocked;
5520
+ }
5521
+ const session = {
5522
+ codeName,
5523
+ startedAt: null,
5524
+ restartCount,
5525
+ status: "starting",
5526
+ port: null,
5527
+ password: null,
5528
+ lastFailureTail: existing?.lastFailureTail ?? null
5529
+ };
5530
+ sessions.set(codeName, session);
5531
+ try {
5532
+ await spawnServe(config2, session);
5533
+ } catch (err) {
5534
+ log2(`[opencode-session] failed to start '${codeName}': ${err.message}`);
5535
+ session.status = "crashed";
5536
+ session.startedAt = Date.now();
5537
+ session.restartCount++;
5538
+ }
5539
+ return session;
5540
+ }
5541
+ async function spawnServe(config2, session) {
5542
+ const { codeName, projectDir, log: log2 } = config2;
5543
+ if (!existsSync9(join18(projectDir, "opencode.json"))) {
5544
+ log2(`[opencode-session] warning: no opencode.json in ${projectDir} for '${codeName}' (provisioning may not have run)`);
5545
+ }
5546
+ const tmuxSession = opencodeTmuxSession(codeName);
5547
+ const port = await findFreePort();
5548
+ const password = randomBytes(24).toString("base64url");
5549
+ try {
5550
+ execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
5551
+ } catch {
5552
+ }
5553
+ mkdirSync6(join18(homedir9(), ".augmented", codeName), { recursive: true });
5554
+ const serveEnv = {
5555
+ ...process.env,
5556
+ ...stripUndefined(config2.serveEnv ?? {}),
5557
+ OPENCODE_SERVER_PASSWORD: password,
5558
+ HOME: process.env.HOME?.trim() || homedir9(),
5559
+ USER: process.env.USER?.trim() || userInfo().username
5560
+ };
5561
+ if (config2.runId) serveEnv["AGT_RUN_ID"] = config2.runId;
5562
+ if (config2.agentTimezone) serveEnv["TZ"] = config2.agentTimezone;
5563
+ const serveCmd = `${OPENCODE_BIN} serve --hostname 127.0.0.1 --port ${port} --print-logs`;
5564
+ log2(`[opencode-session] starting '${tmuxSession}' for '${codeName}' on 127.0.0.1:${port}`);
5565
+ const child = spawn(
5566
+ "tmux",
5567
+ ["new-session", "-d", "-s", tmuxSession, "-c", projectDir, serveCmd],
5568
+ { cwd: projectDir, stdio: ["ignore", "pipe", "pipe"], env: serveEnv }
5569
+ );
5570
+ child.on("close", (code) => {
5571
+ if (code !== 0) {
5572
+ log2(`[opencode-session] failed to create tmux session for '${codeName}' (exit ${code})`);
5573
+ session.status = "crashed";
5574
+ session.startedAt = Date.now();
5575
+ session.restartCount++;
5576
+ return;
5577
+ }
5578
+ log2(`[opencode-session] tmux session '${tmuxSession}' created for '${codeName}'`);
5579
+ setupPaneLog(tmuxSession, codeName, log2);
5580
+ });
5581
+ child.on("error", (err) => {
5582
+ log2(`[opencode-session] failed to start tmux for '${codeName}': ${err.message}`);
5583
+ session.status = "crashed";
5584
+ session.startedAt = Date.now();
5585
+ session.restartCount++;
5586
+ });
5587
+ session.port = port;
5588
+ session.password = password;
5589
+ session.startedAt = Date.now();
5590
+ session.status = "running";
5591
+ session.restartCount = 0;
5592
+ bridges.delete(codeName);
5593
+ }
5594
+ function setupPaneLog(tmuxSession, codeName, log2) {
5595
+ const logPath = opencodePaneLogPath(codeName);
5596
+ try {
5597
+ execSync(`tmux pipe-pane -t ${tmuxSession} -o 'cat >> ${logPath}'`, { stdio: "ignore" });
5598
+ } catch (err) {
5599
+ log2(`[opencode-session] could not attach pane log for '${codeName}': ${err.message}`);
5600
+ }
5601
+ }
5602
+ function readOpencodePaneLogTail(codeName, lines = 40) {
5603
+ const logPath = opencodePaneLogPath(codeName);
5604
+ if (!existsSync9(logPath)) return null;
5605
+ try {
5606
+ const all = readFileSync15(logPath, "utf8").split("\n");
5607
+ return all.slice(-lines).join("\n");
5608
+ } catch {
5609
+ return null;
5610
+ }
5611
+ }
5612
+ function isOpencodeSessionHealthy(codeName) {
5613
+ const tmuxSession = opencodeTmuxSession(codeName);
5614
+ try {
5615
+ execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
5616
+ return true;
5617
+ } catch {
5618
+ return false;
5619
+ }
5620
+ }
5621
+ async function injectOpencodeMessage(codeName, msg, opts = {}) {
5622
+ const session = sessions.get(codeName);
5623
+ if (!session || session.status !== "running" || !session.port || !session.password) {
5624
+ return { status: "declined", reason: "server_not_running" };
5625
+ }
5626
+ const bridge = getBridge(codeName, session.port, session.password);
5627
+ return bridge.handleInbound(msg, { gate: opts.gate, awaitReply: opts.awaitReply });
5628
+ }
5629
+ function getBridge(codeName, port, password) {
5630
+ const cached = bridges.get(codeName);
5631
+ if (cached && cached.port === port && cached.password === password) return cached.bridge;
5632
+ const client2 = new HttpOpencodeClient({ baseUrl: baseUrlFor(port), password });
5633
+ const bridge = new OpencodeInboundBridge({ client: client2 });
5634
+ bridges.set(codeName, { port, password, bridge });
5635
+ return bridge;
5636
+ }
5637
+ function stripUndefined(env) {
5638
+ const out = {};
5639
+ for (const [k, v] of Object.entries(env)) if (v !== void 0) out[k] = v;
5640
+ return out;
5641
+ }
5642
+
5643
+ // src/lib/opencode-slack-ingest.ts
5644
+ import { createHash as createHash11 } from "crypto";
5645
+
5646
+ // ../../packages/core/dist/channels/slack-rich-text.js
5647
+ var MAX_DEPTH = 12;
5648
+ function isRecord(v) {
5649
+ return typeof v === "object" && v !== null && !Array.isArray(v);
5650
+ }
5651
+ function asString(v) {
5652
+ return typeof v === "string" ? v : "";
5653
+ }
5654
+ function asArray(v) {
5655
+ return Array.isArray(v) ? v : [];
5656
+ }
5657
+ function isTableElement(el) {
5658
+ return isRecord(el) && asString(el.type).includes("table");
5659
+ }
5660
+ function renderEmoji(el) {
5661
+ const unicode = asString(el.unicode);
5662
+ if (unicode) {
5663
+ try {
5664
+ const cps = unicode.split("-").map((h) => parseInt(h, 16));
5665
+ if (cps.every((n) => Number.isFinite(n) && n >= 0 && n <= 1114111)) {
5666
+ return String.fromCodePoint(...cps);
5667
+ }
5668
+ } catch {
5669
+ }
5670
+ }
5671
+ const name = asString(el.name);
5672
+ return name ? `:${name}:` : "";
5673
+ }
5674
+ function renderLeaf(el) {
5675
+ if (!isRecord(el))
5676
+ return "";
5677
+ switch (asString(el.type)) {
5678
+ case "text":
5679
+ return asString(el.text);
5680
+ case "link": {
5681
+ const label = asString(el.text);
5682
+ const url = asString(el.url);
5683
+ if (label && url && label !== url)
5684
+ return `${label} (${url})`;
5685
+ return label || url;
5686
+ }
5687
+ case "emoji":
5688
+ return renderEmoji(el);
5689
+ case "user":
5690
+ return `<@${asString(el.user_id)}>`;
5691
+ case "usergroup":
5692
+ return `<!subteam^${asString(el.usergroup_id)}>`;
5693
+ case "channel":
5694
+ return `<#${asString(el.channel_id)}>`;
5695
+ case "broadcast":
5696
+ return `@${asString(el.range) || "channel"}`;
5697
+ case "date":
5698
+ return asString(el.fallback) || asString(el.timestamp);
5699
+ default:
5700
+ if (Array.isArray(el.elements))
5701
+ return renderInline(el.elements);
5702
+ return asString(el.text);
5703
+ }
5704
+ }
5705
+ function renderInline(elements) {
5706
+ return elements.map(renderLeaf).join("");
5707
+ }
5708
+ function deepText(node, depth) {
5709
+ if (depth > MAX_DEPTH)
5710
+ return "";
5711
+ if (typeof node === "string")
5712
+ return node;
5713
+ if (Array.isArray(node))
5714
+ return node.map((n) => deepText(n, depth + 1)).join("");
5715
+ if (!isRecord(node))
5716
+ return "";
5717
+ const type = asString(node.type);
5718
+ if (type && type !== "rich_text" && !type.startsWith("rich_text_") && !type.includes("table")) {
5719
+ return renderLeaf(node);
5720
+ }
5721
+ if (Array.isArray(node.elements))
5722
+ return node.elements.map((n) => deepText(n, depth + 1)).join("");
5723
+ if (Array.isArray(node.rows))
5724
+ return node.rows.map((n) => deepText(n, depth + 1)).join(" ");
5725
+ return asString(node.text);
5726
+ }
5727
+ function renderCell(cell) {
5728
+ if (!isRecord(cell))
5729
+ return deepText(cell, 0).replace(/\s*\n\s*/g, " ").trim();
5730
+ const inner = Array.isArray(cell.elements) ? renderSection(cell, 0) : deepText(cell, 0);
5731
+ return inner.replace(/\s*\n\s*/g, " ").trim();
5732
+ }
5733
+ function renderTable(el) {
5734
+ const rawRows = asArray(el.elements).length ? asArray(el.elements) : asArray(el.rows);
5735
+ const rows = [];
5736
+ for (const row of rawRows) {
5737
+ if (!isRecord(row) && !Array.isArray(row))
5738
+ continue;
5739
+ const rawCells = Array.isArray(row) ? row : asArray(row.elements).length ? asArray(row.elements) : asArray(row.cells);
5740
+ if (rawCells.length === 0)
5741
+ continue;
5742
+ rows.push(rawCells.map(renderCell));
5743
+ }
5744
+ if (rows.length === 0) {
5745
+ const fallback = deepText(el, 0).replace(/[ \t]+\n/g, "\n").trim();
5746
+ return fallback;
5747
+ }
5748
+ const colCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
5749
+ const pad = (r) => {
5750
+ const cells = r.slice();
5751
+ while (cells.length < colCount)
5752
+ cells.push("");
5753
+ return cells.map((c) => c.replace(/\|/g, "\\|"));
5754
+ };
5755
+ const lines = [];
5756
+ const header = rows[0] ?? [];
5757
+ const body = rows.slice(1);
5758
+ lines.push(`| ${pad(header).join(" | ")} |`);
5759
+ lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
5760
+ for (const r of body)
5761
+ lines.push(`| ${pad(r).join(" | ")} |`);
5762
+ return lines.join("\n");
5763
+ }
5764
+ function renderSection(section, depth) {
5765
+ if (depth > MAX_DEPTH || !isRecord(section))
5766
+ return "";
5767
+ const type = asString(section.type);
5768
+ if (isTableElement(section))
5769
+ return renderTable(section);
5770
+ switch (type) {
5771
+ case "rich_text_section":
5772
+ return renderInline(asArray(section.elements));
5773
+ case "rich_text_preformatted": {
5774
+ const code = renderInline(asArray(section.elements));
5775
+ return code ? `\`\`\`
5776
+ ${code}
5777
+ \`\`\`` : "";
5778
+ }
5779
+ case "rich_text_quote": {
5780
+ const quoted = renderInline(asArray(section.elements));
5781
+ return quoted ? quoted.split("\n").map((l) => `> ${l}`).join("\n") : "";
5782
+ }
5783
+ case "rich_text_list": {
5784
+ const ordered = asString(section.style) === "ordered";
5785
+ const items = asArray(section.elements).map((item, i) => {
5786
+ const body = renderSection(item, depth + 1) || renderInline(asArray(item?.elements));
5787
+ const marker = ordered ? `${i + 1}.` : "-";
5788
+ return `${marker} ${body}`.trimEnd();
5789
+ });
5790
+ return items.join("\n");
5791
+ }
5792
+ default:
5793
+ if (Array.isArray(section.elements))
5794
+ return renderInline(asArray(section.elements));
5795
+ return deepText(section, depth + 1);
5796
+ }
5797
+ }
5798
+ function renderSlackBlocks(blocks) {
5799
+ let hadTable = false;
5800
+ const out = [];
5801
+ try {
5802
+ for (const block of asArray(blocks)) {
5803
+ if (!isRecord(block))
5804
+ continue;
5805
+ if (isTableElement(block)) {
5806
+ hadTable = true;
5807
+ const rendered = renderTable(block);
5808
+ if (rendered.trim().length > 0)
5809
+ out.push(rendered);
5810
+ continue;
5811
+ }
5812
+ if (asString(block.type) !== "rich_text")
5813
+ continue;
5814
+ for (const section of asArray(block.elements)) {
5815
+ if (isTableElement(section))
5816
+ hadTable = true;
5817
+ const rendered = renderSection(section, 0);
5818
+ if (rendered.trim().length > 0)
5819
+ out.push(rendered);
5820
+ }
5821
+ }
5822
+ } catch {
5823
+ }
5824
+ const text = out.join("\n\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
5825
+ return { text, hadTable };
5826
+ }
5827
+
5828
+ // ../../packages/core/dist/channels/governance/slack-forwarded.js
5829
+ function asTrimmed(v) {
5830
+ return typeof v === "string" ? v.trim() : "";
5831
+ }
5832
+ function extractForwardedContent(attachments) {
5833
+ if (!Array.isArray(attachments))
5834
+ return { text: "", files: [] };
5835
+ const parts = [];
5836
+ const files = [];
5837
+ for (const raw of attachments) {
5838
+ if (!raw || typeof raw !== "object")
5839
+ continue;
5840
+ const att = raw;
5841
+ const textBody = asTrimmed(att.text);
5842
+ let body = textBody || asTrimmed(att.fallback);
5843
+ const blockSources = [];
5844
+ if (Array.isArray(att.blocks))
5845
+ blockSources.push(att.blocks);
5846
+ if (Array.isArray(att.message_blocks)) {
5847
+ for (const mb of att.message_blocks)
5848
+ blockSources.push(mb?.message?.blocks);
5849
+ }
5850
+ for (const source of blockSources) {
5851
+ const rendered = renderSlackBlocks(source);
5852
+ const renderedText = rendered.text.trim();
5853
+ if (renderedText && (!textBody || rendered.hadTable)) {
5854
+ body = renderedText;
5855
+ break;
5856
+ }
5857
+ }
5858
+ const title = asTrimmed(att.title);
5859
+ const link = asTrimmed(att.title_link) || asTrimmed(att.from_url);
5860
+ const imageUrl = asTrimmed(att.image_url) || asTrimmed(att.thumb_url);
5861
+ const lines = [];
5862
+ if (body)
5863
+ lines.push(body);
5864
+ if (title && title !== body)
5865
+ lines.push(title);
5866
+ if (link)
5867
+ lines.push(link);
5868
+ if (imageUrl)
5869
+ lines.push(`[image] ${imageUrl}`);
5870
+ if (lines.length)
5871
+ parts.push(lines.join("\n"));
5872
+ if (Array.isArray(att.files))
5873
+ files.push(...att.files);
5874
+ }
5875
+ return { text: parts.join("\n\n"), files };
5876
+ }
5877
+ function hasForwardedContent(attachments) {
5878
+ const { text, files } = extractForwardedContent(attachments);
5879
+ return text.length > 0 || files.length > 0;
5880
+ }
5881
+
5882
+ // ../../packages/core/dist/channels/governance/slack-inbound-filter.js
5883
+ var ALLOWED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
5884
+ void 0,
5885
+ "file_share",
5886
+ "thread_broadcast"
5887
+ ]);
5888
+ function decideSlackMessageForward(evt) {
5889
+ if (evt.type !== "message") {
5890
+ return { forward: true };
5891
+ }
5892
+ if (!ALLOWED_MESSAGE_SUBTYPES.has(evt.subtype)) {
5893
+ return { forward: false, reason: "subtype" };
5894
+ }
5895
+ if (!evt.user) {
5896
+ return { forward: false, reason: "no_user" };
5897
+ }
5898
+ const hasText = typeof evt.text === "string" && evt.text.trim().length > 0;
5899
+ const hasFiles = Array.isArray(evt.files) && evt.files.length > 0;
5900
+ const hasBlocks = Array.isArray(evt.blocks) && evt.blocks.length > 0;
5901
+ const hasAttachments = hasForwardedContent(evt.attachments);
5902
+ if (!hasText && !hasFiles && !hasBlocks && !hasAttachments) {
5903
+ return { forward: false, reason: "empty" };
5904
+ }
5905
+ return { forward: true };
5906
+ }
5907
+ function isAppMentionEcho(evt, botUserId) {
5908
+ if (evt.type !== "message")
5909
+ return false;
5910
+ if (!botUserId)
5911
+ return false;
5912
+ if (evt.channel?.startsWith("D"))
5913
+ return false;
5914
+ return typeof evt.text === "string" && evt.text.includes(`<@${botUserId}>`);
5915
+ }
5916
+ function extractAugmentedSlackLabel(evt) {
5917
+ if (evt.metadata?.event_type !== "augmented_agent_message")
5918
+ return null;
5919
+ return {
5920
+ teamId: evt.metadata.event_payload?.["augmented_team_id"],
5921
+ agentId: evt.metadata.event_payload?.["augmented_agent_id"]
5922
+ };
5923
+ }
5924
+ function decideModeForward(evt, policy) {
5925
+ if (policy.mode === "all")
5926
+ return { forward: true };
5927
+ if (policy.mode === "manager_only") {
5928
+ if (policy.principalSlackUserId && evt.user === policy.principalSlackUserId) {
5929
+ return { forward: true };
5930
+ }
5931
+ }
5932
+ if (policy.mode === "team_only") {
5933
+ if (policy.teamPrincipalSlackUserIds && policy.teamPrincipalSlackUserIds.length > 0 && typeof evt.user === "string" && policy.teamPrincipalSlackUserIds.includes(evt.user)) {
5934
+ return { forward: true };
5935
+ }
5936
+ }
5937
+ const label = extractAugmentedSlackLabel(evt);
5938
+ if (!label)
5939
+ return { forward: false, reason: "sender_policy" };
5940
+ if (policy.mode === "team_agents_only" || policy.mode === "manager_only" || policy.mode === "team_only") {
5941
+ if (!policy.teamId || label.teamId !== policy.teamId) {
5942
+ return { forward: false, reason: "sender_policy" };
5943
+ }
5944
+ }
5945
+ return { forward: true };
5946
+ }
5947
+ function decideSenderPolicyForward(evt, policy) {
5948
+ if (policy.internalOnly) {
5949
+ if (!policy.homeTeamId)
5950
+ return { forward: false, reason: "sender_policy" };
5951
+ if (evt.team !== policy.homeTeamId)
5952
+ return { forward: false, reason: "sender_policy" };
5953
+ }
5954
+ return decideModeForward(evt, policy);
5955
+ }
5956
+
5957
+ // ../../packages/core/dist/channels/governance/slack-peer-classifier.js
5958
+ var CODE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
5959
+ function classifyPeerMessage(msg, cfg, self) {
5960
+ if (!msg.bot_id)
5961
+ return { kind: "human" };
5962
+ if (self.bot_user_id !== null && msg.user === self.bot_user_id) {
5963
+ return { kind: "self" };
5964
+ }
5965
+ if (cfg.peer_disabled_mode === "all") {
5966
+ return { kind: "drop", reason: "peer_disabled_all" };
5967
+ }
5968
+ if (cfg.peer_agent_mode === "off") {
5969
+ return { kind: "drop", reason: "mode_off" };
5970
+ }
5971
+ if (cfg.peer_group_ids.length === 0) {
5972
+ if (msg.channel_type !== "im") {
5973
+ return { kind: "drop", reason: "chat_not_allowed" };
5974
+ }
5975
+ } else if (!cfg.peer_group_ids.includes(msg.channel)) {
5976
+ return { kind: "drop", reason: "chat_not_allowed" };
5977
+ }
5978
+ const peer = cfg.peers.find((p) => p.bot_user_id === msg.user);
5979
+ if (!peer) {
5980
+ return { kind: "drop", reason: "unknown_peer" };
5981
+ }
5982
+ if (peer.gate_path === null) {
5983
+ return { kind: "drop", reason: "cross_team_grant_missing" };
5984
+ }
5985
+ if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
5986
+ return { kind: "drop", reason: "peer_disabled_cross_team" };
5987
+ }
5988
+ if (self.bot_user_id === null) {
5989
+ return { kind: "drop", reason: "self_resolution_pending" };
5990
+ }
5991
+ const addressing = classifyAddressing(msg, self.bot_user_id);
5992
+ if (!addressing.addressed) {
5993
+ return { kind: "drop", reason: "not_addressed" };
5994
+ }
5995
+ if (addressing.viaReplyOnly) {
5996
+ const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(msg.channel);
5997
+ const respondMode = cfg.peer_agent_mode === "respond";
5998
+ if (!(inAllowedGroup && respondMode)) {
5999
+ return { kind: "drop", reason: "not_addressed" };
6000
+ }
6001
+ }
6002
+ return { kind: "peer-ingress", peer };
6003
+ }
6004
+ function classifyAddressing(msg, ourBotUserId) {
6005
+ const text = msg.text ?? "";
6006
+ const mentionToken = `<@${ourBotUserId}>`;
6007
+ const viaMention = text.includes(mentionToken);
6008
+ const viaReply = !!msg.thread_ts && msg.thread_ts !== void 0 && msg.parent_user_id === ourBotUserId;
6009
+ if (viaMention) {
6010
+ return { addressed: true, viaReplyOnly: false };
6011
+ }
6012
+ if (viaReply) {
6013
+ return { addressed: true, viaReplyOnly: true };
6014
+ }
6015
+ return { addressed: false, viaReplyOnly: false };
6016
+ }
6017
+ function parsePeersEnv(raw, gateRaw) {
6018
+ if (!raw || raw.trim().length === 0)
6019
+ return [];
6020
+ let parsed;
6021
+ try {
6022
+ parsed = JSON.parse(raw);
6023
+ } catch {
6024
+ return [];
6025
+ }
6026
+ if (!Array.isArray(parsed))
6027
+ return [];
6028
+ const gateMap = /* @__PURE__ */ new Map();
6029
+ let gateConfigInvalid = false;
6030
+ if (gateRaw && gateRaw.trim().length > 0) {
6031
+ try {
6032
+ const gateParsed = JSON.parse(gateRaw);
6033
+ if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
6034
+ gateConfigInvalid = true;
6035
+ } else {
6036
+ for (const [k, v] of Object.entries(gateParsed)) {
6037
+ if (v === null) {
6038
+ gateMap.set(k, null);
6039
+ } else if (typeof v === "string") {
6040
+ const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
6041
+ if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
6042
+ gateMap.set(k, v);
6043
+ } else {
6044
+ gateConfigInvalid = true;
6045
+ break;
6046
+ }
6047
+ } else {
6048
+ gateConfigInvalid = true;
6049
+ break;
6050
+ }
6051
+ }
6052
+ }
6053
+ } catch {
6054
+ gateConfigInvalid = true;
6055
+ }
6056
+ if (gateConfigInvalid) {
6057
+ console.error("[slack-peer-classifier] SLACK_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)");
6058
+ }
6059
+ }
6060
+ const out = [];
6061
+ for (const entry of parsed) {
6062
+ if (entry && typeof entry === "object" && typeof entry.code_name === "string" && CODE_NAME_RE.test(entry.code_name) && typeof entry.agent_id === "string" && typeof entry.bot_user_id === "string" && entry.bot_user_id.length > 0) {
6063
+ const e = entry;
6064
+ const peer = {
6065
+ code_name: e.code_name,
6066
+ bot_user_id: e.bot_user_id,
6067
+ agent_id: e.agent_id
6068
+ };
6069
+ if (gateConfigInvalid) {
6070
+ peer.gate_path = null;
6071
+ } else if (gateMap.has(e.bot_user_id)) {
6072
+ peer.gate_path = gateMap.get(e.bot_user_id) ?? null;
6073
+ }
6074
+ out.push(peer);
6075
+ }
6076
+ }
6077
+ return out;
6078
+ }
6079
+ function parsePeerGroupIdsEnv(raw) {
6080
+ if (!raw)
6081
+ return [];
6082
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
6083
+ }
6084
+ function parsePeerAgentModeEnv(raw) {
6085
+ if (raw === "listen" || raw === "respond")
6086
+ return raw;
6087
+ return "off";
6088
+ }
6089
+
6090
+ // ../../packages/core/dist/channels/governance/sender-policy-decline.js
6091
+ function classifySlackPolicyBlock(evt, policy) {
6092
+ if (policy.internalOnly && (!policy.homeTeamId || evt.team !== policy.homeTeamId)) {
6093
+ return "internal_only";
6094
+ }
6095
+ switch (policy.mode) {
6096
+ case "manager_only":
6097
+ return "mode_manager_only";
6098
+ case "team_only":
6099
+ return "mode_team_only";
6100
+ case "team_agents_only":
6101
+ return "mode_team_agents_only";
6102
+ case "agents_only":
6103
+ return "mode_agents_only";
6104
+ case "all":
6105
+ return "internal_only";
6106
+ }
6107
+ }
6108
+ function politeDeclineCopy(reason) {
6109
+ switch (reason) {
6110
+ case "internal_only":
6111
+ return "Sorry, I can only respond to people inside our organisation. This conversation appears to be from outside.";
6112
+ case "mode_manager_only":
6113
+ return "Sorry, I only respond to my manager in this channel.";
6114
+ case "mode_team_only":
6115
+ return "Sorry, this agent only replies to its team members.";
6116
+ case "mode_team_agents_only":
6117
+ return "Sorry, I only respond to agents on my team in this channel.";
6118
+ case "mode_agents_only":
6119
+ return "Sorry, I only respond to other agents in this channel.";
6120
+ }
6121
+ }
6122
+
6123
+ // ../../packages/core/dist/channels/governance/inbound-access.js
6124
+ function decideInboundAccess(input) {
6125
+ if (!input.content.forward) {
6126
+ return { kind: "drop", reason: `content:${input.content.reason}` };
6127
+ }
6128
+ if (input.isSelf) {
6129
+ return { kind: "drop", reason: "self" };
6130
+ }
6131
+ if (input.orgBoundary && !input.orgBoundary.forward) {
6132
+ return { kind: "drop", reason: "org_boundary" };
6133
+ }
6134
+ if (input.senderPolicy && !input.senderPolicy.forward) {
6135
+ const reason = input.senderPolicy.reason;
6136
+ const declineWorthy = input.sameOrg && reason !== "internal_only";
6137
+ return {
6138
+ kind: "drop",
6139
+ reason: `sender_policy:${reason}`,
6140
+ decline: declineWorthy ? politeDeclineCopy(reason) : void 0
6141
+ };
6142
+ }
6143
+ if (input.command && !input.isBot) {
6144
+ return {
6145
+ kind: "command",
6146
+ command: input.command.command,
6147
+ authorized: input.command.authorized
6148
+ };
6149
+ }
6150
+ if (input.isBot) {
6151
+ const peer = input.peer;
6152
+ if (!peer || peer.kind === "self") {
6153
+ return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
6154
+ }
6155
+ if (peer.kind === "drop") {
6156
+ return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
6157
+ }
6158
+ }
6159
+ return { kind: "admit" };
6160
+ }
6161
+
6162
+ // ../../packages/core/dist/channels/governance/slack-sender-gate.js
6163
+ function evaluateSlackInboundGate(evt, deps) {
6164
+ const isBot = Boolean(evt.bot_id);
6165
+ const content = decideSlackMessageForward(evt);
6166
+ const spDecision = decideSenderPolicyForward(evt, deps.senderPolicy);
6167
+ const senderPolicy = spDecision.forward ? { forward: true } : { forward: false, reason: classifySlackPolicyBlock(evt, deps.senderPolicy) };
6168
+ const peer = isBot ? (() => {
6169
+ const c = classifyPeerMessage({
6170
+ text: evt.text,
6171
+ channel: evt.channel ?? "",
6172
+ channel_type: evt.channel_type,
6173
+ user: evt.user,
6174
+ bot_id: evt.bot_id,
6175
+ thread_ts: evt.thread_ts,
6176
+ parent_user_id: evt.parent_user_id
6177
+ }, deps.peerConfig, { bot_user_id: deps.botUserId ?? null });
6178
+ return { kind: c.kind, reason: c.kind === "drop" ? c.reason : void 0 };
6179
+ })() : void 0;
6180
+ const decision = decideInboundAccess({
6181
+ content,
6182
+ isSelf: Boolean(evt.user && deps.botUserId && evt.user === deps.botUserId),
6183
+ isBot,
6184
+ peer,
6185
+ senderPolicy,
6186
+ // Injection gate: no command dispatch and no same-org decline copy needed.
6187
+ sameOrg: false
6188
+ });
6189
+ if (decision.kind === "drop")
6190
+ return { admit: false, reason: decision.reason };
6191
+ return { admit: true };
6192
+ }
6193
+
6194
+ // ../../packages/core/dist/channels/governance/slack-inbound-config.js
6195
+ function buildSlackSenderPolicyFromEnv(env, ctx) {
6196
+ const raw = (env["SLACK_SENDER_POLICY"] ?? "all").trim().toLowerCase();
6197
+ const internalOnly = env["SLACK_INTERNAL_ONLY"] === "true";
6198
+ const homeTeamId = env["SLACK_HOME_TEAM_ID"];
6199
+ if (internalOnly && !homeTeamId) {
6200
+ throw new Error("SLACK_INTERNAL_ONLY=true requires SLACK_HOME_TEAM_ID. The bot install must have a known team_id (run auth.test against the workspace and persist the result).");
6201
+ }
6202
+ const internalOnlyFields = internalOnly ? { internalOnly: true, homeTeamId } : {};
6203
+ const teamId = ctx.agentTeamId;
6204
+ if (raw === "all")
6205
+ return { mode: "all", ...internalOnlyFields };
6206
+ if (raw === "agents_only")
6207
+ return { mode: "agents_only", ...internalOnlyFields };
6208
+ if (raw === "team_agents_only") {
6209
+ if (!teamId) {
6210
+ throw new Error("SLACK_SENDER_POLICY=team_agents_only requires AGT_TEAM_ID");
6211
+ }
6212
+ return { mode: "team_agents_only", teamId, ...internalOnlyFields };
6213
+ }
6214
+ if (raw === "team_only") {
6215
+ if (!teamId) {
6216
+ throw new Error("SLACK_SENDER_POLICY=team_only requires AGT_TEAM_ID");
6217
+ }
6218
+ const teamPrincipalSlackUserIds = (env["SLACK_SENDER_POLICY_TEAM_PRINCIPAL_IDS"] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6219
+ return { mode: "team_only", teamId, teamPrincipalSlackUserIds, ...internalOnlyFields };
6220
+ }
6221
+ if (raw === "manager_only") {
6222
+ const principalSlackUserId = env["SLACK_SENDER_POLICY_PRINCIPAL_ID"];
6223
+ if (!teamId) {
6224
+ throw new Error("SLACK_SENDER_POLICY=manager_only requires AGT_TEAM_ID");
6225
+ }
6226
+ if (!principalSlackUserId) {
6227
+ throw new Error("SLACK_SENDER_POLICY=manager_only requires SLACK_SENDER_POLICY_PRINCIPAL_ID. Set agent.reports_to_person with a slack_user_id in contact_preferences and re-run /host/refresh.");
6228
+ }
6229
+ return { mode: "manager_only", teamId, principalSlackUserId, ...internalOnlyFields };
6230
+ }
6231
+ throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(env["SLACK_SENDER_POLICY"])}`);
6232
+ }
6233
+ function parseSlackPeerDisabledModeEnv(env, onWarn) {
6234
+ const raw = (env["PEER_DISABLED"] ?? "").trim().toLowerCase();
6235
+ if (raw === "" || raw === "off")
6236
+ return "off";
6237
+ if (raw === "cross_team_only" || raw === "all")
6238
+ return raw;
6239
+ onWarn?.(`invalid PEER_DISABLED=${JSON.stringify(env["PEER_DISABLED"])} - defaulting to 'all' (fail-closed)`);
6240
+ return "all";
6241
+ }
6242
+ function buildSlackPeerClassifierConfigFromEnv(env, opts) {
6243
+ return {
6244
+ peer_agent_mode: parsePeerAgentModeEnv(env["SLACK_PEER_AGENT_MODE"]),
6245
+ peer_group_ids: parsePeerGroupIdsEnv(env["SLACK_PEER_GROUP_IDS"]),
6246
+ peers: parsePeersEnv(env["SLACK_PEERS"], env["SLACK_PEERS_GATE"]),
6247
+ peer_disabled_mode: parseSlackPeerDisabledModeEnv(env, opts?.onWarn)
6248
+ };
6249
+ }
6250
+
6251
+ // src/lib/slack-ingest.ts
6252
+ var SLACK_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
6253
+ "invalid_auth",
6254
+ "token_revoked",
6255
+ "token_expired",
6256
+ "account_inactive",
6257
+ "not_authed"
6258
+ ]);
6259
+ async function resolveSlackBotUserId(botToken, fetchImpl) {
6260
+ const doFetch = fetchImpl ?? globalThis.fetch;
6261
+ try {
6262
+ const res = await doFetch(`${SLACK_API}/auth.test`, {
6263
+ method: "POST",
6264
+ headers: { Authorization: `Bearer ${botToken}` },
6265
+ signal: AbortSignal.timeout(1e4)
6266
+ });
6267
+ const data = await res.json();
6268
+ if (data.ok) return { id: data.user_id, authFailed: false };
6269
+ return { id: void 0, authFailed: !!data.error && SLACK_AUTH_FAILURE_CODES.has(data.error) };
6270
+ } catch {
6271
+ return { id: void 0, authFailed: false };
6272
+ }
6273
+ }
6274
+ function resolveSlackIngestParamsFromEnv(env, ctx) {
6275
+ const appToken = env["SLACK_APP_TOKEN"];
6276
+ const botToken = env["SLACK_BOT_TOKEN"];
6277
+ if (!appToken || !botToken) return null;
6278
+ return {
6279
+ appToken,
6280
+ botToken,
6281
+ senderPolicy: buildSlackSenderPolicyFromEnv(env, ctx),
6282
+ peerConfig: buildSlackPeerClassifierConfigFromEnv(env)
6283
+ };
6284
+ }
6285
+ var BoundedSet = class {
6286
+ max;
6287
+ set = /* @__PURE__ */ new Set();
6288
+ constructor(max = 500) {
6289
+ this.max = max;
6290
+ }
6291
+ has(k) {
6292
+ return this.set.has(k);
6293
+ }
6294
+ add(k) {
6295
+ this.set.add(k);
6296
+ if (this.set.size > this.max) {
6297
+ const oldest = this.set.values().next().value;
6298
+ if (oldest !== void 0) this.set.delete(oldest);
6299
+ }
6300
+ }
6301
+ };
6302
+ async function dispatchSlackInbound(event, ctx) {
6303
+ if (isAppMentionEcho(event, ctx.botUserId ?? null)) {
6304
+ return "dropped_echo";
6305
+ }
6306
+ const dedupKey = event.channel && event.ts ? `${event.channel}:${event.ts}` : null;
6307
+ if (dedupKey) {
6308
+ if (ctx.seen.has(dedupKey)) return "dropped_duplicate";
6309
+ ctx.seen.add(dedupKey);
6310
+ }
6311
+ const decision = evaluateSlackInboundGate(event, {
6312
+ senderPolicy: ctx.senderPolicy,
6313
+ peerConfig: ctx.peerConfig,
6314
+ botUserId: ctx.botUserId
6315
+ });
6316
+ if (!decision.admit) {
6317
+ ctx.log(`[slack-ingest:${ctx.codeName}] dropped ${redact(event.channel)} reason=${decision.reason}`);
6318
+ return "dropped_gate";
6319
+ }
6320
+ const conversationKey = `${event.channel ?? "nochannel"}:${event.thread_ts ?? event.ts ?? ""}`;
6321
+ const meta = {
6322
+ source: "slack",
6323
+ lane: "conversational",
6324
+ requires_reply: "true"
6325
+ };
6326
+ if (event.thread_ts) meta["thread_ts"] = event.thread_ts;
6327
+ else if (event.ts) meta["thread_ts"] = event.ts;
6328
+ const result = await ctx.inject({
6329
+ channelId: "slack",
6330
+ conversationKey,
6331
+ senderId: event.user ?? "unknown",
6332
+ text: event.text ?? "",
6333
+ meta
6334
+ });
6335
+ if (result.status === "declined") {
6336
+ ctx.log(`[slack-ingest:${ctx.codeName}] inject declined reason=${result.reason}`);
6337
+ return "declined";
6338
+ }
6339
+ if (result.status === "replied" && result.reply && event.channel) {
6340
+ await ctx.sendReply({
6341
+ channel: event.channel,
6342
+ text: result.reply,
6343
+ threadTs: event.thread_ts ?? event.ts
6344
+ });
6345
+ return "admitted_replied";
6346
+ }
6347
+ return "admitted_no_reply";
6348
+ }
6349
+ function redact(id) {
6350
+ if (!id) return "?";
6351
+ return `${id.slice(0, 1)}***`;
6352
+ }
6353
+ var SLACK_API = "https://slack.com/api";
6354
+ function defaultSendReply(botToken, fetchImpl, log2) {
6355
+ return async ({ channel, text, threadTs }) => {
6356
+ try {
6357
+ const res = await fetchImpl(`${SLACK_API}/chat.postMessage`, {
6358
+ method: "POST",
6359
+ headers: {
6360
+ Authorization: `Bearer ${botToken}`,
6361
+ "Content-Type": "application/json; charset=utf-8"
6362
+ },
6363
+ body: JSON.stringify({ channel, text, ...threadTs ? { thread_ts: threadTs } : {} })
6364
+ });
6365
+ const data = await res.json();
6366
+ if (!data.ok) log2(`[slack-ingest] chat.postMessage failed: ${data.error}`);
6367
+ } catch (err) {
6368
+ log2(`[slack-ingest] chat.postMessage threw: ${err instanceof Error ? err.message : String(err)}`);
6369
+ }
6370
+ };
6371
+ }
6372
+ function startSlackIngest(config2) {
6373
+ const fetchImpl = config2.fetchImpl ?? globalThis.fetch;
6374
+ const socketFactory = config2.socketFactory ?? ((url) => {
6375
+ const Ctor = globalThis.WebSocket;
6376
+ if (!Ctor) throw new Error("global WebSocket is unavailable (Node 22+ required for opencode Slack ingest)");
6377
+ return new Ctor(url);
6378
+ });
6379
+ const sendReply = config2.sendReply ?? defaultSendReply(config2.botToken, fetchImpl, config2.log);
6380
+ const seen = new BoundedSet();
6381
+ let stopped = false;
6382
+ let ws = null;
6383
+ const ctx = {
6384
+ codeName: config2.codeName,
6385
+ botUserId: config2.botUserId,
6386
+ senderPolicy: config2.senderPolicy,
6387
+ peerConfig: config2.peerConfig,
6388
+ inject: config2.inject,
6389
+ sendReply,
6390
+ log: config2.log,
6391
+ seen
6392
+ };
6393
+ const scheduleReconnect = (delayMs) => {
6394
+ if (stopped) return;
6395
+ setTimeout(() => void connect(), delayMs).unref?.();
6396
+ };
6397
+ const connect = async () => {
6398
+ if (stopped) return;
6399
+ let url;
6400
+ try {
6401
+ const res = await fetchImpl(`${SLACK_API}/apps.connections.open`, {
6402
+ method: "POST",
6403
+ headers: { Authorization: `Bearer ${config2.appToken}` },
6404
+ // Bound the open call so a hung request can't wedge ingest without a
6405
+ // reconnect (Node 22+ required for opencode Slack ingest, so
6406
+ // AbortSignal.timeout is available).
6407
+ signal: AbortSignal.timeout(1e4)
6408
+ });
6409
+ const data = await res.json();
6410
+ if (!data.ok || !data.url) {
6411
+ config2.log(`[slack-ingest:${config2.codeName}] apps.connections.open failed: ${data.error}`);
6412
+ scheduleReconnect(1e4);
6413
+ return;
6414
+ }
6415
+ url = data.url;
6416
+ } catch (err) {
6417
+ config2.log(`[slack-ingest:${config2.codeName}] connect threw: ${err instanceof Error ? err.message : String(err)}`);
6418
+ scheduleReconnect(1e4);
6419
+ return;
6420
+ }
6421
+ if (stopped) return;
6422
+ try {
6423
+ ws = socketFactory(url);
6424
+ ws.onopen = () => config2.log(`[slack-ingest:${config2.codeName}] Socket Mode connected`);
6425
+ ws.onclose = () => {
6426
+ if (!stopped) scheduleReconnect(5e3);
6427
+ };
6428
+ ws.onerror = () => {
6429
+ };
6430
+ ws.onmessage = (ev) => {
6431
+ void onEnvelope(String(ev.data));
6432
+ };
6433
+ } catch (err) {
6434
+ config2.log(`[slack-ingest:${config2.codeName}] socket construction threw: ${err instanceof Error ? err.message : String(err)}`);
6435
+ scheduleReconnect(1e4);
6436
+ }
6437
+ };
6438
+ const onEnvelope = async (raw) => {
6439
+ let msg;
6440
+ try {
6441
+ msg = JSON.parse(raw);
6442
+ } catch {
6443
+ return;
6444
+ }
6445
+ if (msg.envelope_id && ws) {
6446
+ try {
6447
+ ws.send(JSON.stringify({ envelope_id: msg.envelope_id }));
6448
+ } catch {
6449
+ }
6450
+ }
6451
+ if (msg.type === "disconnect") {
6452
+ try {
6453
+ ws?.close();
6454
+ } catch {
6455
+ }
6456
+ return;
6457
+ }
6458
+ if (msg.type !== "events_api") return;
6459
+ const event = msg.payload?.event;
6460
+ if (!event || event.type !== "message" && event.type !== "app_mention") return;
6461
+ try {
6462
+ await dispatchSlackInbound(event, ctx);
6463
+ } catch (err) {
6464
+ config2.log(`[slack-ingest:${config2.codeName}] dispatch threw: ${err instanceof Error ? err.message : String(err)}`);
6465
+ }
6466
+ };
6467
+ void connect();
6468
+ return {
6469
+ stop() {
6470
+ stopped = true;
6471
+ try {
6472
+ ws?.close();
6473
+ } catch {
6474
+ }
6475
+ ws = null;
6476
+ }
6477
+ };
6478
+ }
6479
+
6480
+ // src/lib/opencode-slack-ingest.ts
6481
+ var ingests = /* @__PURE__ */ new Map();
6482
+ function fingerprint(params) {
6483
+ return createHash11("sha256").update(
6484
+ JSON.stringify({
6485
+ appToken: params.appToken,
6486
+ botToken: params.botToken,
6487
+ senderPolicy: params.senderPolicy,
6488
+ peerConfig: params.peerConfig
6489
+ })
6490
+ ).digest("hex");
6491
+ }
6492
+ async function ensureOpencodeSlackIngest(opts) {
6493
+ const { codeName, agentId, log: log2 } = opts;
6494
+ const readEnv = opts.readEnv ?? readChannelServerEnv;
6495
+ let params;
6496
+ let env;
6497
+ try {
6498
+ env = readEnv(codeName, "slack");
6499
+ if (!env) {
6500
+ stopOpencodeSlackIngest(codeName, log2);
6501
+ return;
6502
+ }
6503
+ params = resolveSlackIngestParamsFromEnv(env, { agentTeamId: env["AGT_TEAM_ID"] ?? null });
6504
+ } catch (err) {
6505
+ stopOpencodeSlackIngest(codeName, log2);
6506
+ log2(
6507
+ `[slack-ingest:${codeName}] not started: ${err instanceof Error ? err.message : String(err)}`
6508
+ );
6509
+ return;
6510
+ }
6511
+ if (!params) {
6512
+ stopOpencodeSlackIngest(codeName, log2);
6513
+ return;
6514
+ }
6515
+ const key = fingerprint(params);
6516
+ const existing = ingests.get(codeName);
6517
+ if (existing?.key === key) return;
6518
+ const { id: botUserId, authFailed } = await resolveSlackBotUserId(params.botToken, opts.fetchImpl);
6519
+ if (authFailed) {
6520
+ stopOpencodeSlackIngest(codeName, log2);
6521
+ log2(`[slack-ingest:${codeName}] not started: Slack bot token rejected (needs reconnect)`);
6522
+ return;
6523
+ }
6524
+ if (!botUserId) {
6525
+ log2(`[slack-ingest:${codeName}] bot user id unresolved; echo filtering degraded`);
6526
+ }
6527
+ if (existing) {
6528
+ existing.handle.stop();
6529
+ ingests.delete(codeName);
6530
+ log2(`[slack-ingest:${codeName}] config changed; restarting ingest`);
6531
+ }
6532
+ const handle = startSlackIngest({
6533
+ codeName,
6534
+ agentId,
6535
+ appToken: params.appToken,
6536
+ botToken: params.botToken,
6537
+ botUserId,
6538
+ senderPolicy: params.senderPolicy,
6539
+ peerConfig: params.peerConfig,
6540
+ inject: (msg) => injectOpencodeMessage(codeName, msg, { awaitReply: true }),
6541
+ log: log2,
6542
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
6543
+ ...opts.socketFactory ? { socketFactory: opts.socketFactory } : {}
6544
+ });
6545
+ ingests.set(codeName, { key, handle });
6546
+ log2(`[slack-ingest:${codeName}] ingest started (policy=${params.senderPolicy.mode})`);
6547
+ }
6548
+ function stopOpencodeSlackIngest(codeName, log2) {
6549
+ const entry = ingests.get(codeName);
6550
+ if (!entry) return;
6551
+ entry.handle.stop();
6552
+ ingests.delete(codeName);
6553
+ log2?.(`[slack-ingest:${codeName}] ingest stopped`);
6554
+ }
6555
+ function stopAllOpencodeSlackIngests(log2) {
6556
+ for (const codeName of [...ingests.keys()]) stopOpencodeSlackIngest(codeName, log2);
6557
+ }
6558
+
5441
6559
  // src/lib/wedge-detection.ts
5442
6560
  var DEFAULTS = {
5443
6561
  inboundWaitSeconds: 120,
@@ -5578,24 +6696,24 @@ function partitionActionableByPoison(actionable, states, config2) {
5578
6696
  }
5579
6697
 
5580
6698
  // src/lib/restart-flags.ts
5581
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync15, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
5582
- import { homedir as homedir9 } from "os";
5583
- import { join as join18 } from "path";
6699
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync16, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
6700
+ import { homedir as homedir10 } from "os";
6701
+ import { join as join19 } from "path";
5584
6702
  import { randomUUID } from "crypto";
5585
6703
  function restartFlagsDir() {
5586
- return join18(homedir9(), ".augmented", "restart-flags");
6704
+ return join19(homedir10(), ".augmented", "restart-flags");
5587
6705
  }
5588
6706
  function flagPath(codeName) {
5589
- return join18(restartFlagsDir(), `${codeName}.flag`);
6707
+ return join19(restartFlagsDir(), `${codeName}.flag`);
5590
6708
  }
5591
6709
  function readRestartFlags() {
5592
6710
  const dir = restartFlagsDir();
5593
- if (!existsSync9(dir)) return [];
6711
+ if (!existsSync10(dir)) return [];
5594
6712
  const out = [];
5595
6713
  for (const entry of readdirSync4(dir)) {
5596
6714
  if (!entry.endsWith(".flag")) continue;
5597
6715
  try {
5598
- const raw = readFileSync15(join18(dir, entry), "utf8");
6716
+ const raw = readFileSync16(join19(dir, entry), "utf8");
5599
6717
  const parsed = JSON.parse(raw);
5600
6718
  if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
5601
6719
  parsed.codeName = entry.replace(/\.flag$/, "");
@@ -5613,7 +6731,7 @@ function readRestartFlags() {
5613
6731
  }
5614
6732
  function deleteRestartFlag(codeName) {
5615
6733
  const path = flagPath(codeName);
5616
- if (existsSync9(path)) {
6734
+ if (existsSync10(path)) {
5617
6735
  rmSync3(path, { force: true });
5618
6736
  }
5619
6737
  }
@@ -5959,10 +7077,10 @@ function startRealtimeConfig(config2) {
5959
7077
  filter: filterStr
5960
7078
  }, (payload) => {
5961
7079
  const agent = payload.new;
5962
- const fingerprint = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
7080
+ const fingerprint2 = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
5963
7081
  const prev = lastKnown.get(agent.agent_id);
5964
- if (prev === fingerprint) return;
5965
- lastKnown.set(agent.agent_id, fingerprint);
7082
+ if (prev === fingerprint2) return;
7083
+ lastKnown.set(agent.agent_id, fingerprint2);
5966
7084
  log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
5967
7085
  onConfigChange(agent);
5968
7086
  }).subscribe((status) => {
@@ -6646,10 +7764,21 @@ function scheduleSessionRestart(codeName, delayMs, reason, breakerReason = "hot-
6646
7764
  {
6647
7765
  const baselineAgentId = agentState.codeNameToAgentId.get(codeName);
6648
7766
  if (baselineAgentId) {
6649
- persistSenderPolicyBaselineEntry(
7767
+ persistDeliveryBaselineEntry(
7768
+ "senderPolicy",
6650
7769
  baselineAgentId,
6651
7770
  agentState.knownSenderPolicyHashes.get(baselineAgentId)
6652
7771
  );
7772
+ persistDeliveryBaselineEntry(
7773
+ "slackBehaviour",
7774
+ baselineAgentId,
7775
+ agentState.knownSlackBehaviourHashes.get(baselineAgentId)
7776
+ );
7777
+ persistDeliveryBaselineEntry(
7778
+ "msteamsBehaviour",
7779
+ baselineAgentId,
7780
+ agentState.knownMsTeamsBehaviourHashes.get(baselineAgentId)
7781
+ );
6653
7782
  }
6654
7783
  }
6655
7784
  runningMcpHashes.delete(codeName);
@@ -6836,15 +7965,15 @@ var runningChannelSecretHashes = /* @__PURE__ */ new Map();
6836
7965
  var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
6837
7966
  function projectMcpHash(_codeName, projectDir) {
6838
7967
  try {
6839
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
6840
- return createHash11("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
7968
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
7969
+ return createHash12("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
6841
7970
  } catch {
6842
7971
  return null;
6843
7972
  }
6844
7973
  }
6845
7974
  function projectMcpKeys(_codeName, projectDir) {
6846
7975
  try {
6847
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7976
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6848
7977
  const parsed = JSON.parse(raw);
6849
7978
  const servers = parsed.mcpServers;
6850
7979
  if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
@@ -6862,7 +7991,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
6862
7991
  else runningMcpServerKeys.delete(codeName);
6863
7992
  let launchStructure = null;
6864
7993
  try {
6865
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7994
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6866
7995
  launchStructure = managedMcpStructureHashFromFile(
6867
7996
  JSON.parse(raw),
6868
7997
  isManagedMcpServerKey
@@ -6962,7 +8091,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
6962
8091
  if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
6963
8092
  let mcpJsonForRebind = null;
6964
8093
  try {
6965
- mcpJsonForRebind = JSON.parse(readFileSync16(join19(projectDir, ".mcp.json"), "utf-8"));
8094
+ mcpJsonForRebind = JSON.parse(readFileSync17(join20(projectDir, ".mcp.json"), "utf-8"));
6966
8095
  } catch {
6967
8096
  mcpJsonForRebind = null;
6968
8097
  }
@@ -7091,7 +8220,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
7091
8220
  function projectChannelSecretHash(projectDir) {
7092
8221
  try {
7093
8222
  const entries = parseEnvIntegrations(
7094
- readFileSync16(join19(projectDir, ".env.integrations"), "utf-8")
8223
+ readFileSync17(join20(projectDir, ".env.integrations"), "utf-8")
7095
8224
  );
7096
8225
  return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
7097
8226
  } catch {
@@ -7153,6 +8282,7 @@ function resolveAgentFramework(codeName) {
7153
8282
  function clearAgentCaches(agentId, codeName) {
7154
8283
  const channelCacheMutated = clearAgentState(agentId, codeName);
7155
8284
  agentChannelTokens.delete(codeName);
8285
+ stopOpencodeSlackIngest(codeName, log);
7156
8286
  agentFrameworkCache.delete(codeName);
7157
8287
  kanbanBoardCache.delete(codeName);
7158
8288
  notifyBoardCache.delete(codeName);
@@ -7171,7 +8301,7 @@ function clearAgentCaches(agentId, codeName) {
7171
8301
  mcpFlapDampener.forget(codeName);
7172
8302
  deferLogThrottle.delete(codeName);
7173
8303
  if (channelCacheMutated) saveChannelHashCache2();
7174
- removeSenderPolicyBaselineEntry(agentId);
8304
+ removeDeliveryBaselineEntries(agentId);
7175
8305
  }
7176
8306
  var cachedFrameworkVersion = null;
7177
8307
  var cachedMaintenanceWindow = null;
@@ -7179,7 +8309,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
7179
8309
  var lastVersionCheckAt = 0;
7180
8310
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7181
8311
  var lastResponsivenessProbeAt = 0;
7182
- var agtCliVersion = true ? "0.28.319" : "dev";
8312
+ var agtCliVersion = true ? "0.28.321" : "dev";
7183
8313
  function resolveBrewPath(execFileSync2) {
7184
8314
  try {
7185
8315
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7192,7 +8322,7 @@ function resolveBrewPath(execFileSync2) {
7192
8322
  "/usr/local/bin/brew"
7193
8323
  ];
7194
8324
  for (const path of fallbacks) {
7195
- if (existsSync10(path)) return path;
8325
+ if (existsSync11(path)) return path;
7196
8326
  }
7197
8327
  return null;
7198
8328
  }
@@ -7202,7 +8332,7 @@ function claudeBinaryInstalled(execFileSync2) {
7202
8332
  "/opt/homebrew/bin/claude",
7203
8333
  "/usr/local/bin/claude"
7204
8334
  ];
7205
- if (canonical.some((path) => existsSync10(path))) return true;
8335
+ if (canonical.some((path) => existsSync11(path))) return true;
7206
8336
  try {
7207
8337
  execFileSync2("which", ["claude"], { timeout: 5e3 });
7208
8338
  return true;
@@ -7238,7 +8368,7 @@ async function ensureToolkitCli(toolkitSlug) {
7238
8368
  }
7239
8369
  const { binary, installer, package: pkg, script } = integration.cli_tool;
7240
8370
  const resolvedInstaller = installer ?? "manual";
7241
- const { execFileSync: execFileSync2, execSync } = await import("child_process");
8371
+ const { execFileSync: execFileSync2, execSync: execSync2 } = await import("child_process");
7242
8372
  try {
7243
8373
  execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
7244
8374
  toolkitCliEnsured.add(toolkitSlug);
@@ -7289,7 +8419,7 @@ async function ensureToolkitCli(toolkitSlug) {
7289
8419
  return;
7290
8420
  }
7291
8421
  log(`[toolkit-install] ${toolkitSlug}: running declared install script\u2026`);
7292
- execSync(script, { timeout: 18e4, stdio: "pipe" });
8422
+ execSync2(script, { timeout: 18e4, stdio: "pipe" });
7293
8423
  }
7294
8424
  } catch (err) {
7295
8425
  const e = err;
@@ -7313,8 +8443,8 @@ async function ensureToolkitCli(toolkitSlug) {
7313
8443
  }
7314
8444
  function runAsync(cmd, args, opts) {
7315
8445
  return new Promise((resolve, reject) => {
7316
- import("child_process").then(({ spawn }) => {
7317
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], cwd: opts.cwd });
8446
+ import("child_process").then(({ spawn: spawn2 }) => {
8447
+ const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"], cwd: opts.cwd });
7318
8448
  let stdout = "";
7319
8449
  let stderr = "";
7320
8450
  let settled = false;
@@ -7357,8 +8487,8 @@ function claudeManagedSettingsPath() {
7357
8487
  function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7358
8488
  try {
7359
8489
  let settings = {};
7360
- if (existsSync10(path)) {
7361
- const raw = readFileSync16(path, "utf-8").trim();
8490
+ if (existsSync11(path)) {
8491
+ const raw = readFileSync17(path, "utf-8").trim();
7362
8492
  if (raw) {
7363
8493
  let parsed;
7364
8494
  try {
@@ -7374,7 +8504,7 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7374
8504
  }
7375
8505
  if (settings.channelsEnabled === true) return "ok";
7376
8506
  settings.channelsEnabled = true;
7377
- mkdirSync7(dirname6(path), { recursive: true });
8507
+ mkdirSync8(dirname6(path), { recursive: true });
7378
8508
  writeFileSync8(path, `${JSON.stringify(settings, null, 2)}
7379
8509
  `);
7380
8510
  log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
@@ -7420,7 +8550,7 @@ async function ensureFrameworkBinary(frameworkId) {
7420
8550
  if (!process.env.PATH?.split(":").includes(brewBinDir)) {
7421
8551
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
7422
8552
  }
7423
- if (existsSync10("/home/linuxbrew/.linuxbrew/bin/claude")) {
8553
+ if (existsSync11("/home/linuxbrew/.linuxbrew/bin/claude")) {
7424
8554
  log("Claude Code installed successfully");
7425
8555
  } else {
7426
8556
  log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
@@ -7476,7 +8606,7 @@ ${r.stderr}`;
7476
8606
  }
7477
8607
  var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7478
8608
  function selfUpdateAppliedMarkerPath() {
7479
- return join19(homedir10(), ".augmented", ".last-self-update-applied");
8609
+ return join20(homedir11(), ".augmented", ".last-self-update-applied");
7480
8610
  }
7481
8611
  var selfUpdateUpToDateLogged = false;
7482
8612
  var restartAfterUpgrade = false;
@@ -7500,7 +8630,7 @@ async function checkAndUpdateCli(opts) {
7500
8630
  const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
7501
8631
  if (!isBrewFormula && !isNpmGlobal) return "noop";
7502
8632
  const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
7503
- const markerPath = join19(homedir10(), ".augmented", ".last-update-check");
8633
+ const markerPath = join20(homedir11(), ".augmented", ".last-update-check");
7504
8634
  if (!force) {
7505
8635
  try {
7506
8636
  const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
@@ -7778,13 +8908,13 @@ async function checkClaudeAuth() {
7778
8908
  }
7779
8909
  var evalEmptyMcpConfigPath = null;
7780
8910
  function ensureEvalEmptyMcpConfig() {
7781
- if (evalEmptyMcpConfigPath && existsSync10(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
7782
- const dir = join19(homedir10(), ".augmented");
8911
+ if (evalEmptyMcpConfigPath && existsSync11(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
8912
+ const dir = join20(homedir11(), ".augmented");
7783
8913
  try {
7784
- mkdirSync7(dir, { recursive: true });
8914
+ mkdirSync8(dir, { recursive: true });
7785
8915
  } catch {
7786
8916
  }
7787
- const p = join19(dir, ".eval-empty-mcp.json");
8917
+ const p = join20(dir, ".eval-empty-mcp.json");
7788
8918
  writeFileSync8(p, JSON.stringify({ mcpServers: {} }));
7789
8919
  evalEmptyMcpConfigPath = p;
7790
8920
  return p;
@@ -7810,7 +8940,7 @@ async function runEvalClaude(prompt, model) {
7810
8940
  ""
7811
8941
  ];
7812
8942
  const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), args, {
7813
- cwd: homedir10(),
8943
+ cwd: homedir11(),
7814
8944
  timeout: 12e4,
7815
8945
  stdin: "ignore",
7816
8946
  env: childEnv,
@@ -7876,10 +9006,10 @@ function resolveConversationEvalBackend() {
7876
9006
  return conversationEvalBackend;
7877
9007
  }
7878
9008
  function getStateFile() {
7879
- return join19(config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
9009
+ return join20(config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
7880
9010
  }
7881
9011
  function channelHashCacheDir() {
7882
- return config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9012
+ return config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7883
9013
  }
7884
9014
  function loadChannelHashCache2() {
7885
9015
  loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
@@ -7887,27 +9017,51 @@ function loadChannelHashCache2() {
7887
9017
  function saveChannelHashCache2() {
7888
9018
  saveChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
7889
9019
  }
7890
- var persistedSenderPolicyBaseline = /* @__PURE__ */ new Map();
9020
+ var persistedDeliveryBaseline = createDeliveryBaselineMaps();
7891
9021
  function loadSenderPolicyBaseline2() {
7892
- loadSenderPolicyBaseline(persistedSenderPolicyBaseline, channelHashCacheDir(), log);
7893
- for (const [agentId, hash] of persistedSenderPolicyBaseline) {
9022
+ loadSenderPolicyBaseline(persistedDeliveryBaseline, channelHashCacheDir(), log);
9023
+ for (const [agentId, hash] of persistedDeliveryBaseline.senderPolicy) {
7894
9024
  agentState.knownSenderPolicyHashes.set(agentId, hash);
7895
9025
  }
9026
+ for (const [agentId, hash] of persistedDeliveryBaseline.slackBehaviour) {
9027
+ agentState.knownSlackBehaviourHashes.set(agentId, hash);
9028
+ }
9029
+ for (const [agentId, hash] of persistedDeliveryBaseline.msteamsBehaviour) {
9030
+ agentState.knownMsTeamsBehaviourHashes.set(agentId, hash);
9031
+ }
7896
9032
  }
7897
- function persistSenderPolicyBaselineEntry(agentId, hash) {
9033
+ var deliveryBaselineDirty = false;
9034
+ function saveDeliveryBaseline() {
9035
+ deliveryBaselineDirty = !saveSenderPolicyBaseline(
9036
+ persistedDeliveryBaseline,
9037
+ channelHashCacheDir(),
9038
+ log
9039
+ );
9040
+ }
9041
+ function flushDeliveryBaselineIfDirty() {
9042
+ if (deliveryBaselineDirty) saveDeliveryBaseline();
9043
+ }
9044
+ function persistDeliveryBaselineEntry(concern, agentId, hash) {
7898
9045
  if (hash === void 0) return;
7899
- if (persistedSenderPolicyBaseline.get(agentId) === hash) return;
7900
- persistedSenderPolicyBaseline.set(agentId, hash);
7901
- saveSenderPolicyBaseline(persistedSenderPolicyBaseline, channelHashCacheDir(), log);
9046
+ if (persistedDeliveryBaseline[concern].get(agentId) === hash && !deliveryBaselineDirty) return;
9047
+ persistedDeliveryBaseline[concern].set(agentId, hash);
9048
+ saveDeliveryBaseline();
7902
9049
  }
7903
- function removeSenderPolicyBaselineEntry(agentId) {
7904
- if (!persistedSenderPolicyBaseline.delete(agentId)) return;
7905
- saveSenderPolicyBaseline(persistedSenderPolicyBaseline, channelHashCacheDir(), log);
9050
+ function removeDeliveryBaselineEntry(concern, agentId) {
9051
+ if (!persistedDeliveryBaseline[concern].delete(agentId) && !deliveryBaselineDirty) return;
9052
+ saveDeliveryBaseline();
9053
+ }
9054
+ function removeDeliveryBaselineEntries(agentId) {
9055
+ let mutated = false;
9056
+ for (const map of Object.values(persistedDeliveryBaseline)) {
9057
+ if (map.delete(agentId)) mutated = true;
9058
+ }
9059
+ if (mutated || deliveryBaselineDirty) saveDeliveryBaseline();
7906
9060
  }
7907
9061
  var _channelQuarantineStore = null;
7908
9062
  function channelQuarantineStore() {
7909
9063
  if (!_channelQuarantineStore) {
7910
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9064
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7911
9065
  _channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
7912
9066
  }
7913
9067
  return _channelQuarantineStore;
@@ -7915,7 +9069,7 @@ function channelQuarantineStore() {
7915
9069
  var _hostFlagStore = null;
7916
9070
  function hostFlagStore() {
7917
9071
  if (!_hostFlagStore) {
7918
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9072
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7919
9073
  _hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
7920
9074
  }
7921
9075
  return _hostFlagStore;
@@ -7983,12 +9137,12 @@ function parseSkillFrontmatter(content) {
7983
9137
  }
7984
9138
  async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
7985
9139
  const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync9 } = await import("fs");
7986
- const skillsDir = join19(configDir, codeName, "project", ".claude", "skills");
7987
- const claudeMdPath = join19(configDir, codeName, "project", "CLAUDE.md");
9140
+ const skillsDir = join20(configDir, codeName, "project", ".claude", "skills");
9141
+ const claudeMdPath = join20(configDir, codeName, "project", "CLAUDE.md");
7988
9142
  if (!ex(skillsDir) || !ex(claudeMdPath)) return;
7989
9143
  const entries = [];
7990
9144
  for (const dir of readdirSync6(skillsDir).sort()) {
7991
- const skillFile = join19(skillsDir, dir, "SKILL.md");
9145
+ const skillFile = join20(skillsDir, dir, "SKILL.md");
7992
9146
  if (!ex(skillFile)) continue;
7993
9147
  try {
7994
9148
  const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
@@ -8051,7 +9205,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
8051
9205
  if (codeNames.length === 0) return;
8052
9206
  void (async () => {
8053
9207
  try {
8054
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
9208
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
8055
9209
  await api.post("/host/heartbeat", {
8056
9210
  host_id: hostId,
8057
9211
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -8064,6 +9218,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
8064
9218
  async function pollCycle() {
8065
9219
  if (!config) return;
8066
9220
  if (restartAfterUpgrade) return;
9221
+ flushDeliveryBaselineIfDirty();
8067
9222
  try {
8068
9223
  await processRestartFlags({
8069
9224
  log,
@@ -8149,7 +9304,7 @@ async function pollCycle() {
8149
9304
  }
8150
9305
  try {
8151
9306
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
8152
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
9307
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
8153
9308
  const diagCodeNames = [...agentState.persistentSessionAgents];
8154
9309
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
8155
9310
  let tailscaleHostname;
@@ -8170,8 +9325,8 @@ async function pollCycle() {
8170
9325
  }
8171
9326
  let osUsername;
8172
9327
  try {
8173
- const { userInfo } = await import("os");
8174
- osUsername = userInfo().username;
9328
+ const { userInfo: userInfo2 } = await import("os");
9329
+ osUsername = userInfo2().username;
8175
9330
  } catch {
8176
9331
  }
8177
9332
  let claudeAuth = null;
@@ -8179,7 +9334,7 @@ async function pollCycle() {
8179
9334
  claudeAuth = await detectClaudeAuth();
8180
9335
  } catch (err) {
8181
9336
  const errText = err instanceof Error ? err.message : String(err);
8182
- const errId = createHash11("sha256").update(errText).digest("hex").slice(0, 12);
9337
+ const errId = createHash12("sha256").update(errText).digest("hex").slice(0, 12);
8183
9338
  log(`Claude auth detection failed (error_id=${errId})`);
8184
9339
  }
8185
9340
  const hostHasClaudeCode = state6.agents.some(
@@ -8298,7 +9453,7 @@ async function pollCycle() {
8298
9453
  const {
8299
9454
  collectResponsivenessProbes,
8300
9455
  getResponsivenessIntervalMs
8301
- } = await import("../responsiveness-probe-YIESMTKL.js");
9456
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8302
9457
  const probeIntervalMs = getResponsivenessIntervalMs();
8303
9458
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
8304
9459
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -8330,7 +9485,7 @@ async function pollCycle() {
8330
9485
  collectResponsivenessProbes,
8331
9486
  livePendingInboundOldestAgeSeconds,
8332
9487
  parkPendingInbound
8333
- } = await import("../responsiveness-probe-YIESMTKL.js");
9488
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8334
9489
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
8335
9490
  const wedgeNow = /* @__PURE__ */ new Date();
8336
9491
  const liveAgents = agentState.persistentSessionAgents;
@@ -8419,13 +9574,13 @@ async function pollCycle() {
8419
9574
  );
8420
9575
  if (hostFlagStore().getBoolean("wedge-transient-notice")) {
8421
9576
  try {
8422
- const paneTail = readFileSync16(paneLogPath(codeName), "utf8").slice(-65536);
9577
+ const paneTail = readFileSync17(paneLogPath(codeName), "utf8").slice(-65536);
8423
9578
  const transient = detectTransientApiErrorInLog(paneTail);
8424
9579
  if (transient) {
8425
- const wedgeHome = join19(homedir10(), ".augmented", codeName);
8426
- if (existsSync10(wedgeHome)) {
9580
+ const wedgeHome = join20(homedir11(), ".augmented", codeName);
9581
+ if (existsSync11(wedgeHome)) {
8427
9582
  atomicWriteFileSync(
8428
- join19(wedgeHome, "watchdog-give-up.json"),
9583
+ join20(wedgeHome, "watchdog-give-up.json"),
8429
9584
  JSON.stringify({
8430
9585
  gave_up_at: wedgeNow.toISOString(),
8431
9586
  reason: "transient_overload"
@@ -8674,7 +9829,7 @@ async function pollCycle() {
8674
9829
  } catch {
8675
9830
  }
8676
9831
  killAgentChannelProcesses(prev.codeName, { log });
8677
- const agentDir = join19(adapter.getAgentDir(prev.codeName), "provision");
9832
+ const agentDir = join20(adapter.getAgentDir(prev.codeName), "provision");
8678
9833
  await cleanupAgentFiles(prev.codeName, agentDir);
8679
9834
  clearAgentCaches(prev.agentId, prev.codeName);
8680
9835
  }
@@ -8761,10 +9916,10 @@ async function pollCycle() {
8761
9916
  // pending-inbound marker. Best-effort: a write failure is logged by
8762
9917
  // the watchdog, never fails the poll cycle.
8763
9918
  signalGiveUp: (codeName) => {
8764
- const dir = join19(homedir10(), ".augmented", codeName);
8765
- if (!existsSync10(dir)) return;
9919
+ const dir = join20(homedir11(), ".augmented", codeName);
9920
+ if (!existsSync11(dir)) return;
8766
9921
  atomicWriteFileSync(
8767
- join19(dir, "watchdog-give-up.json"),
9922
+ join20(dir, "watchdog-give-up.json"),
8768
9923
  JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
8769
9924
  );
8770
9925
  }
@@ -8894,7 +10049,7 @@ async function processAgent(agent, agentStates) {
8894
10049
  }
8895
10050
  const now = (/* @__PURE__ */ new Date()).toISOString();
8896
10051
  const adapter = resolveAgentFramework(agent.code_name);
8897
- let agentDir = join19(adapter.getAgentDir(agent.code_name), "provision");
10052
+ let agentDir = join20(adapter.getAgentDir(agent.code_name), "provision");
8898
10053
  if (agent.status === "draft" || agent.status === "paused") {
8899
10054
  if (previousKnownStatus !== agent.status) {
8900
10055
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
@@ -8940,7 +10095,7 @@ async function processAgent(agent, agentStates) {
8940
10095
  const residuals = {
8941
10096
  gatewayRunning: false,
8942
10097
  portAllocated: false,
8943
- provisionDirExists: existsSync10(agentDir)
10098
+ provisionDirExists: existsSync11(agentDir)
8944
10099
  };
8945
10100
  if (!hasRevokedResiduals(residuals)) {
8946
10101
  agentStates.push({
@@ -9078,7 +10233,7 @@ async function processAgent(agent, agentStates) {
9078
10233
  const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
9079
10234
  agentFrameworkCache.set(agent.code_name, frameworkId);
9080
10235
  const frameworkAdapter = getFramework(frameworkId);
9081
- agentDir = join19(frameworkAdapter.getAgentDir(agent.code_name), "provision");
10236
+ agentDir = join20(frameworkAdapter.getAgentDir(agent.code_name), "provision");
9082
10237
  cacheAgentDeliveryMetadata(agent.code_name, refreshData);
9083
10238
  agentRestartTimezoneInputs.set(agent.code_name, {
9084
10239
  agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
@@ -9123,9 +10278,9 @@ async function processAgent(agent, agentStates) {
9123
10278
  try {
9124
10279
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
9125
10280
  const changedFiles = [];
9126
- mkdirSync7(agentDir, { recursive: true });
10281
+ mkdirSync8(agentDir, { recursive: true });
9127
10282
  for (const artifact of artifacts) {
9128
- const filePath = join19(agentDir, artifact.relativePath);
10283
+ const filePath = join20(agentDir, artifact.relativePath);
9129
10284
  let existingHash;
9130
10285
  let newHash;
9131
10286
  let writeContent = artifact.content;
@@ -9144,8 +10299,8 @@ async function processAgent(agent, agentStates) {
9144
10299
  };
9145
10300
  newHash = sha256(stripDynamicSections(artifact.content));
9146
10301
  try {
9147
- const projectClaudeMd = join19(config.configDir, agent.code_name, "project", "CLAUDE.md");
9148
- const existing = readFileSync16(projectClaudeMd, "utf-8");
10302
+ const projectClaudeMd = join20(config.configDir, agent.code_name, "project", "CLAUDE.md");
10303
+ const existing = readFileSync17(projectClaudeMd, "utf-8");
9149
10304
  existingHash = sha256(stripDynamicSections(existing));
9150
10305
  } catch {
9151
10306
  existingHash = null;
@@ -9163,7 +10318,7 @@ async function processAgent(agent, agentStates) {
9163
10318
  const generatorKeys = Object.keys(generatorServers);
9164
10319
  let existingRaw = "";
9165
10320
  try {
9166
- existingRaw = readFileSync16(filePath, "utf-8");
10321
+ existingRaw = readFileSync17(filePath, "utf-8");
9167
10322
  } catch {
9168
10323
  }
9169
10324
  const existingServers = parseMcp(existingRaw);
@@ -9185,13 +10340,13 @@ async function processAgent(agent, agentStates) {
9185
10340
  }
9186
10341
  }
9187
10342
  if (changedFiles.length > 0) {
9188
- const isFirst = !existsSync10(join19(agentDir, "CHARTER.md"));
10343
+ const isFirst = !existsSync11(join20(agentDir, "CHARTER.md"));
9189
10344
  const verb = isFirst ? "Provisioning" : "Updating";
9190
10345
  const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
9191
10346
  log(`${verb} '${agent.code_name}': ${fileNames}`);
9192
10347
  for (const file of changedFiles) {
9193
- const filePath = join19(agentDir, file.relativePath);
9194
- mkdirSync7(dirname6(filePath), { recursive: true });
10348
+ const filePath = join20(agentDir, file.relativePath);
10349
+ mkdirSync8(dirname6(filePath), { recursive: true });
9195
10350
  if (file.relativePath === ".mcp.json") {
9196
10351
  safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
9197
10352
  } else {
@@ -9199,12 +10354,12 @@ async function processAgent(agent, agentStates) {
9199
10354
  }
9200
10355
  }
9201
10356
  try {
9202
- const provSkillsDir = join19(agentDir, ".claude", "skills");
9203
- if (existsSync10(provSkillsDir)) {
10357
+ const provSkillsDir = join20(agentDir, ".claude", "skills");
10358
+ if (existsSync11(provSkillsDir)) {
9204
10359
  for (const folder of readdirSync5(provSkillsDir)) {
9205
10360
  if (folder.startsWith("knowledge-")) {
9206
10361
  try {
9207
- rmSync4(join19(provSkillsDir, folder), { recursive: true });
10362
+ rmSync4(join20(provSkillsDir, folder), { recursive: true });
9208
10363
  } catch {
9209
10364
  }
9210
10365
  }
@@ -9217,7 +10372,7 @@ async function processAgent(agent, agentStates) {
9217
10372
  const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
9218
10373
  const hashes = /* @__PURE__ */ new Map();
9219
10374
  for (const file of trackedFiles2) {
9220
- const h = hashFile(join19(agentDir, file));
10375
+ const h = hashFile(join20(agentDir, file));
9221
10376
  if (h) hashes.set(file, h);
9222
10377
  }
9223
10378
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -9235,14 +10390,14 @@ async function processAgent(agent, agentStates) {
9235
10390
  }
9236
10391
  if (Array.isArray(refreshData.workflows)) {
9237
10392
  try {
9238
- const provWorkflowsDir = join19(agentDir, ".claude", "workflows");
9239
- if (existsSync10(provWorkflowsDir)) {
10393
+ const provWorkflowsDir = join20(agentDir, ".claude", "workflows");
10394
+ if (existsSync11(provWorkflowsDir)) {
9240
10395
  const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
9241
10396
  for (const file of readdirSync5(provWorkflowsDir)) {
9242
10397
  if (!file.endsWith(".js")) continue;
9243
10398
  if (expected.has(file)) continue;
9244
10399
  try {
9245
- rmSync4(join19(provWorkflowsDir, file));
10400
+ rmSync4(join20(provWorkflowsDir, file));
9246
10401
  } catch {
9247
10402
  }
9248
10403
  }
@@ -9299,10 +10454,10 @@ async function processAgent(agent, agentStates) {
9299
10454
  }
9300
10455
  let lastDriftCheckAt = now;
9301
10456
  const written = agentState.writtenHashes.get(agent.agent_id);
9302
- if (written && existsSync10(agentDir)) {
10457
+ if (written && existsSync11(agentDir)) {
9303
10458
  const driftedFiles = [];
9304
10459
  for (const [file, expectedHash] of written) {
9305
- const localHash = hashFile(join19(agentDir, file));
10460
+ const localHash = hashFile(join20(agentDir, file));
9306
10461
  if (localHash && localHash !== expectedHash) {
9307
10462
  driftedFiles.push(file);
9308
10463
  }
@@ -9313,7 +10468,7 @@ async function processAgent(agent, agentStates) {
9313
10468
  try {
9314
10469
  const localHashes = {};
9315
10470
  for (const file of driftedFiles) {
9316
- localHashes[file] = hashFile(join19(agentDir, file));
10471
+ localHashes[file] = hashFile(join20(agentDir, file));
9317
10472
  }
9318
10473
  await api.post("/host/drift", {
9319
10474
  agent_id: agent.agent_id,
@@ -9500,15 +10655,15 @@ async function processAgent(agent, agentStates) {
9500
10655
  const addedChannels = [...restartDecision.added];
9501
10656
  const writeDmNoticeMarkers = isChannelAddRestart ? () => {
9502
10657
  try {
9503
- const agentAugmentedDir = join19(homedir10(), ".augmented", agent.code_name);
9504
- mkdirSync7(agentAugmentedDir, { recursive: true });
10658
+ const agentAugmentedDir = join20(homedir11(), ".augmented", agent.code_name);
10659
+ mkdirSync8(agentAugmentedDir, { recursive: true });
9505
10660
  const markerJson = JSON.stringify({
9506
10661
  version: 1,
9507
10662
  at: (/* @__PURE__ */ new Date()).toISOString(),
9508
10663
  added: addedChannels
9509
10664
  });
9510
10665
  for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
9511
- atomicWriteFileSync(join19(agentAugmentedDir, file), markerJson);
10666
+ atomicWriteFileSync(join20(agentAugmentedDir, file), markerJson);
9512
10667
  }
9513
10668
  } catch (err) {
9514
10669
  log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
@@ -9569,14 +10724,14 @@ async function processAgent(agent, agentStates) {
9569
10724
  agentState.knownSenderPolicyHashes.set(agent.agent_id, senderPolicyHash);
9570
10725
  const hasLiveSenderPolicyConsumer = hasSenderPolicyChannel && spSessionHealthy && spSessionMode === "persistent" && spFramework === "claude-code";
9571
10726
  if (!hasLiveSenderPolicyConsumer) {
9572
- persistSenderPolicyBaselineEntry(agent.agent_id, senderPolicyHash);
10727
+ persistDeliveryBaselineEntry("senderPolicy", agent.agent_id, senderPolicyHash);
9573
10728
  } else if (senderPolicyDecision.firstPoll && !senderPolicyRestrictive) {
9574
- if (persistedSenderPolicyBaseline.get(agent.agent_id) !== senderPolicyHash) {
10729
+ if (persistedDeliveryBaseline.senderPolicy.get(agent.agent_id) !== senderPolicyHash) {
9575
10730
  log(
9576
10731
  `[sender-policy] baseline established without restart for '${agent.code_name}' (policy is non-restrictive); a policy loosened while the baseline was unknown requires an agent restart to take effect (ENG-7771)`
9577
10732
  );
9578
10733
  }
9579
- persistSenderPolicyBaselineEntry(agent.agent_id, senderPolicyHash);
10734
+ persistDeliveryBaselineEntry("senderPolicy", agent.agent_id, senderPolicyHash);
9580
10735
  }
9581
10736
  let msteamsBehaviourRestartScheduled = false;
9582
10737
  if (currentChannelIds.has("msteams")) {
@@ -9584,20 +10739,16 @@ async function processAgent(agent, agentStates) {
9584
10739
  const behaviourSubset = extractMsTeamsBehaviourSubset(
9585
10740
  msteamsEntry?.config
9586
10741
  );
9587
- const behaviourHash = createHash11("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
10742
+ const behaviourHash = createHash12("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
9588
10743
  const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
10744
+ const msteamsBehaviourRestrictive = isMsTeamsBehaviourRestrictive(behaviourSubset);
9589
10745
  const behaviourDecision = decideSenderPolicyRestart({
9590
10746
  previousHash: prevBehaviourHash,
9591
- // ENG-7771 scoped the persisted-baseline + first-poll fail-safe to
9592
- // sender_policy only; the behaviour baselines keep the legacy
9593
- // "first poll never restarts" semantics until the follow-up that
9594
- // migrates them onto the sidecar (their maps share the same
9595
- // wipe-on-manager-restart pathology).
9596
- currentPolicyRestrictive: false,
10747
+ currentPolicyRestrictive: msteamsBehaviourRestrictive,
9597
10748
  currentHash: behaviourHash,
9598
- sessionMode: refreshData.agent.session_mode,
9599
- framework: agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK,
9600
- sessionHealthy: isSessionHealthy(agent.code_name),
10749
+ sessionMode: spSessionMode,
10750
+ framework: spFramework,
10751
+ sessionHealthy: spSessionHealthy,
9601
10752
  // A channel-set or sender-policy restart scheduled this tick will
9602
10753
  // respawn the MCP children with the freshly-written env for free.
9603
10754
  channelSetRestartAlreadyScheduled: channelSetRestartScheduled || senderPolicyDecision.restart
@@ -9629,35 +10780,42 @@ async function processAgent(agent, agentStates) {
9629
10780
  msteamsBehaviourRestartScheduled = true;
9630
10781
  }
9631
10782
  agentState.knownMsTeamsBehaviourHashes.set(agent.agent_id, behaviourHash);
10783
+ const hasLiveMsTeamsBehaviourConsumer = spSessionHealthy && spSessionMode === "persistent" && spFramework === "claude-code";
10784
+ if (!hasLiveMsTeamsBehaviourConsumer) {
10785
+ persistDeliveryBaselineEntry("msteamsBehaviour", agent.agent_id, behaviourHash);
10786
+ } else if (behaviourDecision.firstPoll && !msteamsBehaviourRestrictive) {
10787
+ persistDeliveryBaselineEntry("msteamsBehaviour", agent.agent_id, behaviourHash);
10788
+ }
9632
10789
  } else {
9633
10790
  agentState.knownMsTeamsBehaviourHashes.delete(agent.agent_id);
10791
+ removeDeliveryBaselineEntry("msteamsBehaviour", agent.agent_id);
9634
10792
  }
9635
10793
  if (currentChannelIds.has("slack")) {
9636
10794
  const slackEntry = refreshData.channel_configs?.["slack"];
9637
10795
  const slackBehaviourSubset = extractSlackBehaviourSubset(
9638
10796
  slackEntry?.config
9639
10797
  );
9640
- const slackBehaviourHash = createHash11("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
10798
+ const slackBehaviourHash = createHash12("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
9641
10799
  const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
10800
+ const slackBehaviourRestrictive = isSlackBehaviourRestrictive(slackBehaviourSubset);
9642
10801
  const slackBehaviourDecision = decideSenderPolicyRestart({
9643
10802
  previousHash: prevSlackBehaviourHash,
9644
- // See the msteams call above — behaviour baselines are out of
9645
- // ENG-7771's scope; legacy first-poll semantics until the follow-up.
9646
- currentPolicyRestrictive: false,
10803
+ currentPolicyRestrictive: slackBehaviourRestrictive,
9647
10804
  currentHash: slackBehaviourHash,
9648
- sessionMode: refreshData.agent.session_mode,
9649
- framework: agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK,
9650
- sessionHealthy: isSessionHealthy(agent.code_name),
10805
+ sessionMode: spSessionMode,
10806
+ framework: spFramework,
10807
+ sessionHealthy: spSessionHealthy,
9651
10808
  // A channel-set, sender-policy, or msteams-behaviour restart
9652
10809
  // scheduled this tick respawns the MCP children with the
9653
10810
  // freshly-written env for free.
9654
10811
  channelSetRestartAlreadyScheduled: channelSetRestartScheduled || senderPolicyDecision.restart || msteamsBehaviourRestartScheduled
9655
10812
  });
9656
10813
  if (slackBehaviourDecision.restart) {
10814
+ const isSlackVerifyRestart = slackBehaviourDecision.firstPoll;
9657
10815
  log(
9658
- `[hot-reload] slack behaviour settings changed for '${agent.code_name}' (${prevSlackBehaviourHash?.slice(0, 8) ?? "first"} \u2192 ${slackBehaviourHash.slice(0, 8)}) \u2014 restarting session`
10816
+ isSlackVerifyRestart ? `[hot-reload] slack behaviour delivery baseline unknown for '${agent.code_name}' with a restrictive allowed_users list (\u2192 ${slackBehaviourHash.slice(0, 8)}) \u2014 restarting session to guarantee enforcement (fail closed, ENG-7779)` : `[hot-reload] slack behaviour settings changed for '${agent.code_name}' (${prevSlackBehaviourHash?.slice(0, 8) ?? "first"} \u2192 ${slackBehaviourHash.slice(0, 8)}) \u2014 restarting session`
9659
10817
  );
9660
- const slackBehaviourNotice = "Your Slack channel behaviour settings were updated. Restarting session shortly so the slack-channel MCP picks up the new configuration.";
10818
+ const slackBehaviourNotice = isSlackVerifyRestart ? "Your manager was updated and needs to restart your session to make sure your configured Slack access controls are enforced by the channel MCP server." : "Your Slack channel behaviour settings were updated. Restarting session shortly so the slack-channel MCP picks up the new configuration.";
9661
10819
  const slackBehaviourDelivered = await injectMessage(
9662
10820
  agent.code_name,
9663
10821
  "system",
@@ -9674,37 +10832,44 @@ async function processAgent(agent, agentStates) {
9674
10832
  scheduleSessionRestart(
9675
10833
  agent.code_name,
9676
10834
  slackBehaviourDelay,
9677
- "slack behaviour change",
10835
+ isSlackVerifyRestart ? "slack behaviour verify (unknown baseline)" : "slack behaviour change",
9678
10836
  "channel-behaviour-change"
9679
10837
  );
9680
10838
  }
9681
10839
  agentState.knownSlackBehaviourHashes.set(agent.agent_id, slackBehaviourHash);
10840
+ const hasLiveSlackBehaviourConsumer = spSessionHealthy && spSessionMode === "persistent" && spFramework === "claude-code";
10841
+ if (!hasLiveSlackBehaviourConsumer) {
10842
+ persistDeliveryBaselineEntry("slackBehaviour", agent.agent_id, slackBehaviourHash);
10843
+ } else if (slackBehaviourDecision.firstPoll && !slackBehaviourRestrictive) {
10844
+ persistDeliveryBaselineEntry("slackBehaviour", agent.agent_id, slackBehaviourHash);
10845
+ }
9682
10846
  } else {
9683
10847
  agentState.knownSlackBehaviourHashes.delete(agent.agent_id);
10848
+ removeDeliveryBaselineEntry("slackBehaviour", agent.agent_id);
9684
10849
  }
9685
10850
  }
9686
10851
  const agentSessionMode = refreshData.agent.session_mode;
9687
10852
  if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
9688
10853
  try {
9689
10854
  const agentProvisionDir = agentDir;
9690
- const projectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
9691
- mkdirSync7(agentProvisionDir, { recursive: true });
9692
- mkdirSync7(projectDir, { recursive: true });
9693
- const provisionMcpPath = join19(agentProvisionDir, ".mcp.json");
9694
- const projectMcpPath = join19(projectDir, ".mcp.json");
10855
+ const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
10856
+ mkdirSync8(agentProvisionDir, { recursive: true });
10857
+ mkdirSync8(projectDir, { recursive: true });
10858
+ const provisionMcpPath = join20(agentProvisionDir, ".mcp.json");
10859
+ const projectMcpPath = join20(projectDir, ".mcp.json");
9695
10860
  let mcpConfig = { mcpServers: {} };
9696
10861
  try {
9697
- mcpConfig = JSON.parse(readFileSync16(provisionMcpPath, "utf-8"));
10862
+ mcpConfig = JSON.parse(readFileSync17(provisionMcpPath, "utf-8"));
9698
10863
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
9699
10864
  } catch {
9700
10865
  }
9701
- const localDirectChatChannel = join19(homedir10(), ".augmented", "_mcp", "direct-chat-channel.js");
10866
+ const localDirectChatChannel = join20(homedir11(), ".augmented", "_mcp", "direct-chat-channel.js");
9702
10867
  const directChatTeamSettings = refreshData.team?.settings;
9703
10868
  const directChatTz = (() => {
9704
10869
  const tz = directChatTeamSettings?.["timezone"];
9705
10870
  return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
9706
10871
  })();
9707
- if (existsSync10(localDirectChatChannel)) {
10872
+ if (existsSync11(localDirectChatChannel)) {
9708
10873
  const directChatEnv = {
9709
10874
  AGT_HOST: requireHost(),
9710
10875
  // ENG-5901 Track D: templated — the manager exports the real
@@ -9724,7 +10889,7 @@ async function processAgent(agent, agentStates) {
9724
10889
  // ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
9725
10890
  // returns the agent root (NOT the /provision subdir `agentDir` points at),
9726
10891
  // so it byte-matches the broker readers' path.
9727
- AGT_TURN_INITIATOR_FILE: join19(
10892
+ AGT_TURN_INITIATOR_FILE: join20(
9728
10893
  frameworkAdapter.getAgentDir(agent.code_name),
9729
10894
  ".current-turn-initiator.json"
9730
10895
  )
@@ -9744,8 +10909,8 @@ async function processAgent(agent, agentStates) {
9744
10909
  log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
9745
10910
  }
9746
10911
  }
9747
- const staleChannelsPath = join19(projectDir, ".mcp-channels.json");
9748
- if (existsSync10(staleChannelsPath)) {
10912
+ const staleChannelsPath = join20(projectDir, ".mcp-channels.json");
10913
+ if (existsSync11(staleChannelsPath)) {
9749
10914
  try {
9750
10915
  rmSync4(staleChannelsPath, { force: true });
9751
10916
  } catch {
@@ -9834,7 +10999,7 @@ async function processAgent(agent, agentStates) {
9834
10999
  }
9835
11000
  if (hostFlagStore().getBoolean("connectivity-probe")) {
9836
11001
  try {
9837
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
11002
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9838
11003
  await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
9839
11004
  } catch (err) {
9840
11005
  log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
@@ -9845,7 +11010,7 @@ async function processAgent(agent, agentStates) {
9845
11010
  const forceDue = attemptsLeft > 0;
9846
11011
  let probeRan = false;
9847
11012
  try {
9848
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
11013
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9849
11014
  probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
9850
11015
  } catch (err) {
9851
11016
  log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
@@ -9858,9 +11023,9 @@ async function processAgent(agent, agentStates) {
9858
11023
  }
9859
11024
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
9860
11025
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
9861
- if (mcpPath && existsSync10(mcpPath)) {
11026
+ if (mcpPath && existsSync11(mcpPath)) {
9862
11027
  try {
9863
- const cfg = JSON.parse(readFileSync16(mcpPath, "utf-8"));
11028
+ const cfg = JSON.parse(readFileSync17(mcpPath, "utf-8"));
9864
11029
  const expectedRemoteKeys = new Set(
9865
11030
  integrations.map((i) => i.definition_id)
9866
11031
  );
@@ -9885,11 +11050,11 @@ async function processAgent(agent, agentStates) {
9885
11050
  recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
9886
11051
  }
9887
11052
  if (intHash !== prevIntHash) {
9888
- const projectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
9889
- const envIntPath = join19(projectDir, ".env.integrations");
11053
+ const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
11054
+ const envIntPath = join20(projectDir, ".env.integrations");
9890
11055
  let preWriteEnv;
9891
11056
  try {
9892
- preWriteEnv = readFileSync16(envIntPath, "utf-8");
11057
+ preWriteEnv = readFileSync17(envIntPath, "utf-8");
9893
11058
  } catch {
9894
11059
  preWriteEnv = void 0;
9895
11060
  }
@@ -9901,9 +11066,9 @@ async function processAgent(agent, agentStates) {
9901
11066
  let rotationHandled = true;
9902
11067
  if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
9903
11068
  try {
9904
- const projectMcpPath = join19(projectDir, ".mcp.json");
9905
- const postWriteEnv = readFileSync16(envIntPath, "utf-8");
9906
- const mcpContent = readFileSync16(projectMcpPath, "utf-8");
11069
+ const projectMcpPath = join20(projectDir, ".mcp.json");
11070
+ const postWriteEnv = readFileSync17(envIntPath, "utf-8");
11071
+ const mcpContent = readFileSync17(projectMcpPath, "utf-8");
9907
11072
  const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
9908
11073
  const mcpJsonForReap = JSON.parse(mcpContent);
9909
11074
  const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
@@ -9980,10 +11145,10 @@ async function processAgent(agent, agentStates) {
9980
11145
  desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
9981
11146
  }
9982
11147
  const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
9983
- const headersHash = createHash11("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
11148
+ const headersHash = createHash12("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
9984
11149
  return `${e.serverId}|${e.url}|${headersHash}`;
9985
11150
  }).join("\n");
9986
- const mcpHash = createHash11("sha256").update(hashBasis).digest("hex").slice(0, 16);
11151
+ const mcpHash = createHash12("sha256").update(hashBasis).digest("hex").slice(0, 16);
9987
11152
  const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
9988
11153
  const structureHash = managedMcpStructureHash(desiredEntries);
9989
11154
  const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
@@ -9998,15 +11163,15 @@ async function processAgent(agent, agentStates) {
9998
11163
  if (mcpHash !== prevMcpHash) {
9999
11164
  for (const e of desiredEntries) {
10000
11165
  frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
10001
- const urlHash = createHash11("sha256").update(e.url).digest("hex").slice(0, 12);
11166
+ const urlHash = createHash12("sha256").update(e.url).digest("hex").slice(0, 12);
10002
11167
  log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
10003
11168
  }
10004
11169
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
10005
11170
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
10006
11171
  if (mcpPath) {
10007
11172
  try {
10008
- const { readFileSync: readFileSync17 } = await import("fs");
10009
- const mcpConfig = JSON.parse(readFileSync17(mcpPath, "utf-8"));
11173
+ const { readFileSync: readFileSync18 } = await import("fs");
11174
+ const mcpConfig = JSON.parse(readFileSync18(mcpPath, "utf-8"));
10010
11175
  if (mcpConfig.mcpServers) {
10011
11176
  for (const key of Object.keys(mcpConfig.mcpServers)) {
10012
11177
  if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
@@ -10107,7 +11272,7 @@ async function processAgent(agent, agentStates) {
10107
11272
  if (frameworkAdapter.installSkillFiles) {
10108
11273
  const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
10109
11274
  const installedIntegrationSkills = [];
10110
- const { createHash: createHash12 } = await import("crypto");
11275
+ const { createHash: createHash13 } = await import("crypto");
10111
11276
  const refreshAny = refreshData;
10112
11277
  const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
10113
11278
  const contextBySlug = /* @__PURE__ */ new Map();
@@ -10136,7 +11301,7 @@ async function processAgent(agent, agentStates) {
10136
11301
  )
10137
11302
  }));
10138
11303
  const bundle = buildIntegrationBundle(renderedScopes);
10139
- const contentHash = createHash12("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
11304
+ const contentHash = createHash13("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
10140
11305
  const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
10141
11306
  if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
10142
11307
  frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
@@ -10149,18 +11314,18 @@ async function processAgent(agent, agentStates) {
10149
11314
  }
10150
11315
  try {
10151
11316
  const { readdirSync: readdirSync6, rmSync: rmSync5 } = await import("fs");
10152
- const { homedir: homedir11 } = await import("os");
11317
+ const { homedir: homedir12 } = await import("os");
10153
11318
  const frameworkId2 = frameworkAdapter.id;
10154
11319
  const candidateSkillDirs = [
10155
11320
  // Claude Code — framework runtime tree
10156
- join19(homedir11(), ".augmented", agent.code_name, "skills"),
11321
+ join20(homedir12(), ".augmented", agent.code_name, "skills"),
10157
11322
  // Claude Code — project tree
10158
- join19(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
11323
+ join20(homedir12(), ".augmented", agent.code_name, "project", ".claude", "skills"),
10159
11324
  // Defensive: legacy provision-side path, not currently an
10160
11325
  // install target but cheap to sweep.
10161
- join19(agentDir, ".claude", "skills")
11326
+ join20(agentDir, ".claude", "skills")
10162
11327
  ];
10163
- const existingDirs = candidateSkillDirs.filter((d) => existsSync10(d));
11328
+ const existingDirs = candidateSkillDirs.filter((d) => existsSync11(d));
10164
11329
  const discoveredEntries = /* @__PURE__ */ new Set();
10165
11330
  for (const dir of existingDirs) {
10166
11331
  try {
@@ -10174,8 +11339,8 @@ async function processAgent(agent, agentStates) {
10174
11339
  }
10175
11340
  const removeSkillFolder = (entry, reason) => {
10176
11341
  for (const dir of existingDirs) {
10177
- const p = join19(dir, entry);
10178
- if (existsSync10(p)) {
11342
+ const p = join20(dir, entry);
11343
+ if (existsSync11(p)) {
10179
11344
  rmSync5(p, { recursive: true, force: true });
10180
11345
  }
10181
11346
  }
@@ -10194,7 +11359,7 @@ async function processAgent(agent, agentStates) {
10194
11359
  const sharedSkillsPayload = refreshAny.shared_skills;
10195
11360
  const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
10196
11361
  const manifestPath = managedSkillManifestPath(
10197
- join19(homedir10(), ".augmented", agent.code_name)
11362
+ join20(homedir11(), ".augmented", agent.code_name)
10198
11363
  );
10199
11364
  const prevIds = /* @__PURE__ */ new Set([
10200
11365
  ...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
@@ -10203,7 +11368,7 @@ async function processAgent(agent, agentStates) {
10203
11368
  const plan = planGlobalSkillSync(
10204
11369
  [...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
10205
11370
  prevIds,
10206
- (content) => createHash12("sha256").update(content).digest("hex").slice(0, 12),
11371
+ (content) => createHash13("sha256").update(content).digest("hex").slice(0, 12),
10207
11372
  (skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
10208
11373
  { desiredResolved }
10209
11374
  );
@@ -10214,15 +11379,15 @@ async function processAgent(agent, agentStates) {
10214
11379
  }
10215
11380
  if (plan.removes.length) {
10216
11381
  const globalSkillDirs = [
10217
- join19(homedir10(), ".augmented", agent.code_name, "skills"),
10218
- join19(homedir10(), ".augmented", agent.code_name, "project", ".claude", "skills"),
10219
- join19(agentDir, ".claude", "skills")
11382
+ join20(homedir11(), ".augmented", agent.code_name, "skills"),
11383
+ join20(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
11384
+ join20(agentDir, ".claude", "skills")
10220
11385
  ];
10221
11386
  for (const id of plan.removes) {
10222
11387
  let prunedAny = false;
10223
11388
  for (const dir of globalSkillDirs) {
10224
- const p = join19(dir, id);
10225
- if (existsSync10(p) && existsSync10(join19(p, "SKILL.md"))) {
11389
+ const p = join20(dir, id);
11390
+ if (existsSync11(p) && existsSync11(join20(p, "SKILL.md"))) {
10226
11391
  rmSync4(p, { recursive: true, force: true });
10227
11392
  prunedAny = true;
10228
11393
  }
@@ -10254,7 +11419,7 @@ async function processAgent(agent, agentStates) {
10254
11419
  const slug = hook.integration_slug ?? hook.plugin_slug;
10255
11420
  if (!slug) continue;
10256
11421
  try {
10257
- const scriptHash = createHash12("sha256").update(hook.script).digest("hex").slice(0, 12);
11422
+ const scriptHash = createHash13("sha256").update(hook.script).digest("hex").slice(0, 12);
10258
11423
  const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
10259
11424
  if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
10260
11425
  const result = await frameworkAdapter.executePluginHook({
@@ -10269,9 +11434,9 @@ async function processAgent(agent, agentStates) {
10269
11434
  } else if (result.timedOut) {
10270
11435
  log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
10271
11436
  } else {
10272
- const stderrHash = createHash12("sha256").update(result.stderr).digest("hex").slice(0, 12);
11437
+ const stderrHash = createHash13("sha256").update(result.stderr).digest("hex").slice(0, 12);
10273
11438
  const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
10274
- const missingCmdHash = missingCmd ? createHash12("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
11439
+ const missingCmdHash = missingCmd ? createHash13("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
10275
11440
  log(
10276
11441
  `Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
10277
11442
  );
@@ -10425,8 +11590,8 @@ async function processAgent(agent, agentStates) {
10425
11590
  const sess = getSessionState(agent.code_name);
10426
11591
  let mcpJsonParsed = null;
10427
11592
  try {
10428
- const mcpPath = join19(getProjectDir(agent.code_name), ".mcp.json");
10429
- mcpJsonParsed = JSON.parse(readFileSync16(mcpPath, "utf-8"));
11593
+ const mcpPath = join20(getProjectDir(agent.code_name), ".mcp.json");
11594
+ mcpJsonParsed = JSON.parse(readFileSync17(mcpPath, "utf-8"));
10430
11595
  } catch {
10431
11596
  }
10432
11597
  reapMissingMcpSessions({
@@ -10751,10 +11916,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10751
11916
  }
10752
11917
  }
10753
11918
  const trackedFiles = frameworkAdapter.driftTrackedFiles();
10754
- if (trackedFiles.length > 0 && existsSync10(agentDir)) {
11919
+ if (trackedFiles.length > 0 && existsSync11(agentDir)) {
10755
11920
  const hashes = /* @__PURE__ */ new Map();
10756
11921
  for (const file of trackedFiles) {
10757
- const h = hashFile(join19(agentDir, file));
11922
+ const h = hashFile(join20(agentDir, file));
10758
11923
  if (h) hashes.set(file, h);
10759
11924
  }
10760
11925
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -10769,7 +11934,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10769
11934
  refreshData.agent.onboarding_state
10770
11935
  );
10771
11936
  const obStep = obState.step;
10772
- const markerPath = join19(homedir10(), ".augmented", agent.code_name, "onboarding-drive.json");
11937
+ const markerPath = join20(homedir11(), ".augmented", agent.code_name, "onboarding-drive.json");
10773
11938
  const marker = readOnboardingDriveMarker(markerPath);
10774
11939
  const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
10775
11940
  if (decision.clearMarker) {
@@ -10830,11 +11995,55 @@ function deriveEgressAllowlist(toolsRaw) {
10830
11995
  );
10831
11996
  }
10832
11997
  var egressAllowlistEqual = (a, b) => a.length === b.length && a.every((d, i) => d === b[i]);
11998
+ async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
11999
+ const codeName = agent.code_name;
12000
+ if (isOpencodeSessionHealthy(codeName)) {
12001
+ await ensureOpencodeSlackIngest({ codeName, agentId: agent.agent_id, log });
12002
+ return { decision: "healthy", spawnAttempted: false, sessionHealthyAfter: true };
12003
+ }
12004
+ stopOpencodeSlackIngest(codeName, log);
12005
+ const opencodeProjectDir = join20(getFramework("opencode").getAgentDir(codeName), "project");
12006
+ const openRouterRaw = refreshData.openrouter ?? null;
12007
+ const serveEnv = {
12008
+ AGT_HOST: requireHost(),
12009
+ AGT_API_KEY: getApiKey() ?? void 0,
12010
+ AGT_APP_URL: process.env["AGT_APP_URL"]
12011
+ };
12012
+ if (openRouterRaw?.auth_token) serveEnv["OPENROUTER_API_KEY"] = openRouterRaw.auth_token;
12013
+ const session = await startOpencodeSession({
12014
+ codeName,
12015
+ agentId: agent.agent_id,
12016
+ projectDir: opencodeProjectDir,
12017
+ serveEnv,
12018
+ agentTimezone,
12019
+ // ADR-0047: the opencode container launcher is not built, so the launcher
12020
+ // refuses to spawn under Docker isolation rather than run unisolated on a
12021
+ // multi-tenant host. Single-agent / dedicated hosts (isolation off) run bare.
12022
+ isolated: isolationMode(codeName) === "docker",
12023
+ log
12024
+ });
12025
+ agentState.persistentSessionAgents.add(codeName);
12026
+ if (session.status === "crashed") {
12027
+ const tail = session.lastFailureTail ?? readOpencodePaneLogTail(codeName);
12028
+ return {
12029
+ decision: "spawn",
12030
+ spawnAttempted: true,
12031
+ sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
12032
+ detail: tail ? `opencode spawn failed: ${tail.slice(0, 200)}` : "opencode spawn failed"
12033
+ };
12034
+ }
12035
+ return {
12036
+ decision: "spawn",
12037
+ spawnAttempted: true,
12038
+ sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
12039
+ detail: `opencode serve on 127.0.0.1:${session.port ?? "?"}`
12040
+ };
12041
+ }
10833
12042
  async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10834
12043
  const codeName = agent.code_name;
10835
12044
  const projectDir = getProjectDir(codeName);
10836
- const mcpConfigPath = join19(projectDir, ".mcp.json");
10837
- const claudeMdPath = join19(projectDir, "CLAUDE.md");
12045
+ const mcpConfigPath = join20(projectDir, ".mcp.json");
12046
+ const claudeMdPath = join20(projectDir, "CLAUDE.md");
10838
12047
  if (restartBreaker.isTripped(codeName)) {
10839
12048
  const trip = restartBreaker.getTrip(codeName);
10840
12049
  return {
@@ -10856,6 +12065,10 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10856
12065
  }
10857
12066
  return resolveAgentTimezone(ownTz, teamTz, orgTierTz);
10858
12067
  })();
12068
+ const frameworkId = agentFrameworkCache.get(codeName) ?? DEFAULT_FRAMEWORK;
12069
+ if (frameworkId === "opencode") {
12070
+ return ensureOpencodeRuntime(agent, refreshData, agentTimezone ?? null);
12071
+ }
10859
12072
  const channelConfigs = refreshData.channel_configs;
10860
12073
  const { devChannels, pluginChannels } = resolveChannelLaunchFlags(
10861
12074
  channelConfigs,
@@ -10988,7 +12201,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10988
12201
  const ctx = getLastFailureContext(codeName);
10989
12202
  const recovery = prepareForRespawn(codeName);
10990
12203
  const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
10991
- ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
12204
+ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash12("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
10992
12205
  const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
10993
12206
  const recoverySummary = recovery ? `; recovery=${recovery}` : "";
10994
12207
  log(
@@ -11002,7 +12215,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
11002
12215
  );
11003
12216
  getHostId().then((hostId) => {
11004
12217
  if (!hostId) return;
11005
- const paneTailHash = zombie.paneTail ? `sha256:${createHash11("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
12218
+ const paneTailHash = zombie.paneTail ? `sha256:${createHash12("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
11006
12219
  return api.post("/host/events", {
11007
12220
  host_id: hostId,
11008
12221
  agent_code_name: codeName,
@@ -11151,7 +12364,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
11151
12364
  if (!claudeAuthTupleBySession.has(codeName)) {
11152
12365
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
11153
12366
  }
11154
- const stableTasksHash = createHash11("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
12367
+ const stableTasksHash = createHash12("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
11155
12368
  const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
11156
12369
  if (stableTasksHash !== prevHash) {
11157
12370
  const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
@@ -11368,7 +12581,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
11368
12581
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
11369
12582
  void (async () => {
11370
12583
  try {
11371
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
12584
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11372
12585
  await api.post("/host/heartbeat", {
11373
12586
  host_id: hostId,
11374
12587
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11418,7 +12631,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
11418
12631
  }
11419
12632
  try {
11420
12633
  const hostId = await getHostId();
11421
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
12634
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11422
12635
  await api.post("/host/heartbeat", {
11423
12636
  host_id: hostId,
11424
12637
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11639,6 +12852,46 @@ async function pollDirectChatMessages(agentStates) {
11639
12852
  }
11640
12853
  }
11641
12854
  }
12855
+ async function processDirectChatMessageOpencode(agent, msg) {
12856
+ if (!isOpencodeSessionHealthy(agent.codeName)) {
12857
+ log(`[direct-chat] opencode server not running for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
12858
+ return;
12859
+ }
12860
+ let result;
12861
+ try {
12862
+ result = await injectOpencodeMessage(agent.codeName, {
12863
+ channelId: "direct-chat",
12864
+ conversationKey: msg.session_id,
12865
+ senderId: "webapp",
12866
+ text: msg.content,
12867
+ // Provenance parity with the Claude Code <channel> envelope: source +
12868
+ // reply-expected, folded into the prompt by the bridge.
12869
+ meta: { source: "direct-chat", user: "webapp", lane: "conversational", requires_reply: "true" }
12870
+ });
12871
+ } catch (err) {
12872
+ log(`[direct-chat] opencode inject failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
12873
+ return;
12874
+ }
12875
+ if (result.status === "replied" && result.reply) {
12876
+ try {
12877
+ await api.post("/host/direct-chat/reply", {
12878
+ agent_id: agent.agentId,
12879
+ session_id: msg.session_id,
12880
+ content: result.reply,
12881
+ message_ids: [msg.id]
12882
+ });
12883
+ log(`[direct-chat] opencode replied for '${agent.codeName}' (msg=${msg.id}, ${result.reply.length} chars)`);
12884
+ } catch (err) {
12885
+ log(`[direct-chat] opencode reply POST failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
12886
+ }
12887
+ return;
12888
+ }
12889
+ if (result.status === "declined") {
12890
+ log(`[direct-chat] opencode gate declined message for '${agent.codeName}' (msg=${msg.id}): ${result.reason}`);
12891
+ return;
12892
+ }
12893
+ log(`[direct-chat] opencode produced no reply for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
12894
+ }
11642
12895
  async function processDirectChatMessage(agent, msg) {
11643
12896
  noteInbound(agent.codeName);
11644
12897
  const fw = agentFrameworkCache.get(agent.codeName) ?? DEFAULT_FRAMEWORK;
@@ -11655,12 +12908,16 @@ async function processDirectChatMessage(agent, msg) {
11655
12908
  }
11656
12909
  return;
11657
12910
  }
12911
+ if (fw === "opencode") {
12912
+ await processDirectChatMessageOpencode(agent, msg);
12913
+ return;
12914
+ }
11658
12915
  if (isSessionHealthy(agent.codeName)) {
11659
12916
  const useDoorbell = hostFlagStore().getBoolean("direct-chat-doorbell") || isolationMode(agent.codeName) === "docker";
11660
12917
  if (useDoorbell) {
11661
12918
  try {
11662
- const doorbell = directChatDoorbellPath(agent.agentId, homedir10());
11663
- mkdirSync7(dirname6(doorbell), { recursive: true });
12919
+ const doorbell = directChatDoorbellPath(agent.agentId, homedir11());
12920
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11664
12921
  writeFileSync8(doorbell, String(Date.now()));
11665
12922
  log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
11666
12923
  return;
@@ -11748,8 +13005,8 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
11748
13005
  }
11749
13006
  if (run_id) openInjectedRunByCode.set(codeName, run_id);
11750
13007
  try {
11751
- const doorbell = directChatDoorbellPath(agentId, homedir10());
11752
- mkdirSync7(dirname6(doorbell), { recursive: true });
13008
+ const doorbell = directChatDoorbellPath(agentId, homedir11());
13009
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11753
13010
  writeFileSync8(doorbell, String(Date.now()));
11754
13011
  } catch (err) {
11755
13012
  log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
@@ -11842,7 +13099,7 @@ async function processClaudePairSessions(agents) {
11842
13099
  killPairSession,
11843
13100
  pairTmuxSession,
11844
13101
  finalizeClaudePairOnboarding
11845
- } = await import("../claude-pair-runtime-55LJJ7DB.js");
13102
+ } = await import("../claude-pair-runtime-DO6OWWGD.js");
11846
13103
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11847
13104
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11848
13105
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -11877,12 +13134,12 @@ async function processClaudePairSessions(agents) {
11877
13134
  try {
11878
13135
  if (session.status === "initiating") {
11879
13136
  log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);
11880
- const spawn = await spawnPairSession(pairSession);
11881
- if (!spawn.ok) {
13137
+ const spawn2 = await spawnPairSession(pairSession);
13138
+ if (!spawn2.ok) {
11882
13139
  await reportAndCleanup(session.pair_id, {
11883
13140
  status: "failure",
11884
- error_code: spawn.error.kind,
11885
- error_message: spawn.error.kind === "unknown" ? spawn.error.message : void 0
13141
+ error_code: spawn2.error.kind,
13142
+ error_message: spawn2.error.kind === "unknown" ? spawn2.error.message : void 0
11886
13143
  });
11887
13144
  continue;
11888
13145
  }
@@ -12085,8 +13342,8 @@ function parseMemoryFile(raw, fallbackName) {
12085
13342
  };
12086
13343
  }
12087
13344
  async function syncMemories(agent, configDir, log2) {
12088
- const projectDir = join19(configDir, agent.code_name, "project");
12089
- const memoryDir = join19(projectDir, "memory");
13345
+ const projectDir = join20(configDir, agent.code_name, "project");
13346
+ const memoryDir = join20(projectDir, "memory");
12090
13347
  const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
12091
13348
  if (isFreshSync) {
12092
13349
  log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
@@ -12097,15 +13354,15 @@ async function syncMemories(agent, configDir, log2) {
12097
13354
  }
12098
13355
  pendingFreshMemorySync.delete(agent.agent_id);
12099
13356
  }
12100
- if (existsSync10(memoryDir)) {
13357
+ if (existsSync11(memoryDir)) {
12101
13358
  const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
12102
13359
  const currentHashes = /* @__PURE__ */ new Map();
12103
13360
  const changedMemories = [];
12104
13361
  for (const file of readdirSync5(memoryDir)) {
12105
13362
  if (!file.endsWith(".md")) continue;
12106
13363
  try {
12107
- const raw = readFileSync16(join19(memoryDir, file), "utf-8");
12108
- const fileHash = createHash11("sha256").update(raw).digest("hex").slice(0, 16);
13364
+ const raw = readFileSync17(join20(memoryDir, file), "utf-8");
13365
+ const fileHash = createHash12("sha256").update(raw).digest("hex").slice(0, 16);
12109
13366
  currentHashes.set(file, fileHash);
12110
13367
  if (prevHashes.get(file) === fileHash) continue;
12111
13368
  const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
@@ -12129,7 +13386,7 @@ async function syncMemories(agent, configDir, log2) {
12129
13386
  } catch (err) {
12130
13387
  for (const mem of changedMemories) {
12131
13388
  for (const [file] of currentHashes) {
12132
- const parsed = parseMemoryFile(readFileSync16(join19(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
13389
+ const parsed = parseMemoryFile(readFileSync17(join20(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
12133
13390
  if (parsed?.name === mem.name) currentHashes.delete(file);
12134
13391
  }
12135
13392
  }
@@ -12142,29 +13399,29 @@ async function syncMemories(agent, configDir, log2) {
12142
13399
  }
12143
13400
  }
12144
13401
  async function downloadMemories(agent, memoryDir, log2, { force }) {
12145
- const localFiles = existsSync10(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
12146
- const localListHash = createHash11("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
13402
+ const localFiles = existsSync11(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
13403
+ const localListHash = createHash12("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
12147
13404
  const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
12148
13405
  const prevDownload = lastDownloadHash.get(agent.agent_id);
12149
13406
  try {
12150
13407
  const dbMemories = await api.post("/host/memories", {
12151
13408
  agent_id: agent.agent_id
12152
13409
  });
12153
- const responseHash = createHash11("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
13410
+ const responseHash = createHash12("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
12154
13411
  if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
12155
13412
  return true;
12156
13413
  }
12157
13414
  lastDownloadHash.set(agent.agent_id, responseHash);
12158
13415
  lastLocalFileHash.set(agent.agent_id, localListHash);
12159
13416
  if (dbMemories.memories?.length) {
12160
- mkdirSync7(memoryDir, { recursive: true });
13417
+ mkdirSync8(memoryDir, { recursive: true });
12161
13418
  let written = 0;
12162
13419
  let overwritten = 0;
12163
13420
  for (let i = 0; i < dbMemories.memories.length; i++) {
12164
13421
  const mem = dbMemories.memories[i];
12165
13422
  const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
12166
13423
  const slug = rawSlug || `memory-${i}`;
12167
- const filePath = join19(memoryDir, `${slug}.md`);
13424
+ const filePath = join20(memoryDir, `${slug}.md`);
12168
13425
  const desired = `---
12169
13426
  name: ${JSON.stringify(mem.name)}
12170
13427
  type: ${mem.type}
@@ -12173,10 +13430,10 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
12173
13430
 
12174
13431
  ${mem.content}
12175
13432
  `;
12176
- if (existsSync10(filePath)) {
13433
+ if (existsSync11(filePath)) {
12177
13434
  let existing = "";
12178
13435
  try {
12179
- existing = readFileSync16(filePath, "utf-8");
13436
+ existing = readFileSync17(filePath, "utf-8");
12180
13437
  } catch {
12181
13438
  }
12182
13439
  if (existing === desired) continue;
@@ -12189,7 +13446,7 @@ ${mem.content}
12189
13446
  }
12190
13447
  if (written > 0 || overwritten > 0) {
12191
13448
  const updatedFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort();
12192
- lastLocalFileHash.set(agent.agent_id, createHash11("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
13449
+ lastLocalFileHash.set(agent.agent_id, createHash12("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
12193
13450
  log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
12194
13451
  }
12195
13452
  }
@@ -12200,7 +13457,7 @@ ${mem.content}
12200
13457
  }
12201
13458
  }
12202
13459
  async function cleanupAgentFiles(codeName, agentDir) {
12203
- if (existsSync10(agentDir)) {
13460
+ if (existsSync11(agentDir)) {
12204
13461
  try {
12205
13462
  rmSync4(agentDir, { recursive: true, force: true });
12206
13463
  log(`Removed provision directory for '${codeName}'`);
@@ -12224,8 +13481,8 @@ var caffeinateProc = null;
12224
13481
  async function startCaffeinate() {
12225
13482
  if (process.platform !== "darwin") return;
12226
13483
  try {
12227
- const { spawn } = await import("child_process");
12228
- caffeinateProc = spawn("caffeinate", ["-dims"], {
13484
+ const { spawn: spawn2 } = await import("child_process");
13485
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
12229
13486
  stdio: "ignore",
12230
13487
  detached: false
12231
13488
  });
@@ -12372,8 +13629,8 @@ async function killAllAgtTmuxSessions() {
12372
13629
  ["list-sessions", "-F", "#{session_name}"],
12373
13630
  { timeout: 2e3, stdin: "ignore" }
12374
13631
  );
12375
- const sessions = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
12376
- for (const session of sessions) {
13632
+ const sessions2 = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
13633
+ for (const session of sessions2) {
12377
13634
  try {
12378
13635
  await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
12379
13636
  timeout: 2e3,
@@ -12383,8 +13640,8 @@ async function killAllAgtTmuxSessions() {
12383
13640
  } catch {
12384
13641
  }
12385
13642
  }
12386
- if (sessions.length > 0) {
12387
- log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);
13643
+ if (sessions2.length > 0) {
13644
+ log(`Cleaned up ${sessions2.length} agt-* tmux session(s)`);
12388
13645
  }
12389
13646
  } catch {
12390
13647
  }
@@ -12420,6 +13677,7 @@ async function stopPolling(opts = {}) {
12420
13677
  const subsysStartedAt = Date.now();
12421
13678
  stopCaffeinate();
12422
13679
  stopRealtimeChat();
13680
+ stopAllOpencodeSlackIngests(log);
12423
13681
  log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
12424
13682
  for (const codeName of [...scheduledRunsByCode.keys()]) {
12425
13683
  closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
@@ -12434,8 +13692,8 @@ function startManager(opts) {
12434
13692
  config = opts;
12435
13693
  try {
12436
13694
  const stateFile = getStateFile();
12437
- if (existsSync10(stateFile)) {
12438
- const raw = readFileSync16(stateFile, "utf-8");
13695
+ if (existsSync11(stateFile)) {
13696
+ const raw = readFileSync17(stateFile, "utf-8");
12439
13697
  const parsed = JSON.parse(raw);
12440
13698
  if (Array.isArray(parsed.agents)) {
12441
13699
  state6.agents = parsed.agents;
@@ -12462,7 +13720,7 @@ function startManager(opts) {
12462
13720
  log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
12463
13721
  }
12464
13722
  log(
12465
- `[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join19(homedir10(), ".augmented", "manager.log")}`
13723
+ `[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join20(homedir11(), ".augmented", "manager.log")}`
12466
13724
  );
12467
13725
  deployMcpAssets();
12468
13726
  reapOrphanChannelMcps({ log });
@@ -12483,7 +13741,7 @@ async function reapOrphanedClaudePids() {
12483
13741
  const looksLikeClaude = (pid) => {
12484
13742
  if (process.platform !== "linux") return true;
12485
13743
  try {
12486
- const comm = readFileSync16(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
13744
+ const comm = readFileSync17(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
12487
13745
  return comm.includes("claude");
12488
13746
  } catch {
12489
13747
  return false;
@@ -12501,16 +13759,16 @@ async function reapOrphanedClaudePids() {
12501
13759
  },
12502
13760
  looksLikeClaude
12503
13761
  );
12504
- for (const spawn of decision.toKill) {
13762
+ for (const spawn2 of decision.toKill) {
12505
13763
  try {
12506
- process.kill(spawn.pid, "SIGKILL");
13764
+ process.kill(spawn2.pid, "SIGKILL");
12507
13765
  } catch (err) {
12508
- log(`[drain] reaper SIGKILL pid=${spawn.pid} failed: ${err.message}`);
13766
+ log(`[drain] reaper SIGKILL pid=${spawn2.pid} failed: ${err.message}`);
12509
13767
  }
12510
13768
  }
12511
- for (const spawn of decision.pidReusedSkipped) {
13769
+ for (const spawn2 of decision.pidReusedSkipped) {
12512
13770
  log(
12513
- `[drain] reaper skipped pid=${spawn.pid} \u2014 PID reuse suspected (identity probe rejected)`
13771
+ `[drain] reaper skipped pid=${spawn2.pid} \u2014 PID reuse suspected (identity probe rejected)`
12514
13772
  );
12515
13773
  }
12516
13774
  log(formatReaperBootLine({
@@ -12580,14 +13838,14 @@ function restartRunningChannelMcps(basenames) {
12580
13838
  }
12581
13839
  }
12582
13840
  function deployMcpAssets() {
12583
- const targetDir = join19(homedir10(), ".augmented", "_mcp");
12584
- mkdirSync7(targetDir, { recursive: true });
13841
+ const targetDir = join20(homedir11(), ".augmented", "_mcp");
13842
+ mkdirSync8(targetDir, { recursive: true });
12585
13843
  const moduleDir = dirname6(fileURLToPath(import.meta.url));
12586
13844
  let mcpSourceDir = "";
12587
13845
  let dir = moduleDir;
12588
13846
  for (let i = 0; i < 6; i++) {
12589
- const candidate = join19(dir, "dist", "mcp");
12590
- if (existsSync10(join19(candidate, "index.js"))) {
13847
+ const candidate = join20(dir, "dist", "mcp");
13848
+ if (existsSync11(join20(candidate, "index.js"))) {
12591
13849
  mcpSourceDir = candidate;
12592
13850
  break;
12593
13851
  }
@@ -12602,8 +13860,8 @@ function deployMcpAssets() {
12602
13860
  const changedBasenames = [];
12603
13861
  const fileHash = (p) => {
12604
13862
  try {
12605
- if (!existsSync10(p)) return null;
12606
- return createHash11("sha256").update(readFileSync16(p)).digest("hex");
13863
+ if (!existsSync11(p)) return null;
13864
+ return createHash12("sha256").update(readFileSync17(p)).digest("hex");
12607
13865
  } catch {
12608
13866
  return null;
12609
13867
  }
@@ -12667,9 +13925,9 @@ function deployMcpAssets() {
12667
13925
  // needs restarting to pick up a token rotation.
12668
13926
  "xero.js"
12669
13927
  ]) {
12670
- const src = join19(mcpSourceDir, file);
12671
- const dst = join19(targetDir, file);
12672
- if (!existsSync10(src)) continue;
13928
+ const src = join20(mcpSourceDir, file);
13929
+ const dst = join20(targetDir, file);
13930
+ if (!existsSync11(src)) continue;
12673
13931
  const before = fileHash(dst);
12674
13932
  try {
12675
13933
  copyFileSync(src, dst);
@@ -12686,16 +13944,16 @@ function deployMcpAssets() {
12686
13944
  log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
12687
13945
  restartRunningChannelMcps(changedBasenames);
12688
13946
  }
12689
- const localMcpPath = join19(targetDir, "index.js");
13947
+ const localMcpPath = join20(targetDir, "index.js");
12690
13948
  try {
12691
- const agentsDir = join19(homedir10(), ".augmented", "agents");
12692
- if (existsSync10(agentsDir)) {
13949
+ const agentsDir = join20(homedir11(), ".augmented", "agents");
13950
+ if (existsSync11(agentsDir)) {
12693
13951
  for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
12694
13952
  if (!entry.isDirectory()) continue;
12695
13953
  for (const subdir of ["provision", "project"]) {
12696
- const mcpJsonPath = join19(agentsDir, entry.name, subdir, ".mcp.json");
13954
+ const mcpJsonPath = join20(agentsDir, entry.name, subdir, ".mcp.json");
12697
13955
  try {
12698
- const raw = readFileSync16(mcpJsonPath, "utf-8");
13956
+ const raw = readFileSync17(mcpJsonPath, "utf-8");
12699
13957
  if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
12700
13958
  const mcpConfig = JSON.parse(raw);
12701
13959
  const augServer = mcpConfig.mcpServers?.["augmented"];