@integrity-labs/agt-cli 0.28.319 → 0.28.320

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-CJSATK6B.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
@@ -5438,6 +5441,1103 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
5438
5441
  }
5439
5442
  }
5440
5443
 
5444
+ // src/lib/opencode-session.ts
5445
+ import { spawn, execSync } from "child_process";
5446
+ import { createServer } from "net";
5447
+ import { randomBytes } from "crypto";
5448
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync15 } from "fs";
5449
+ import { homedir as homedir9, userInfo } from "os";
5450
+ import { join as join18 } from "path";
5451
+ var OPENCODE_BIN = process.env["AGT_OPENCODE_BIN"]?.trim() || "opencode";
5452
+ var sessions = /* @__PURE__ */ new Map();
5453
+ var bridges = /* @__PURE__ */ new Map();
5454
+ function opencodeTmuxSession(codeName) {
5455
+ return `agt-oc-${codeName}`;
5456
+ }
5457
+ function opencodePaneLogPath(codeName) {
5458
+ return join18(homedir9(), ".augmented", codeName, "opencode-serve.log");
5459
+ }
5460
+ function baseUrlFor(port) {
5461
+ return `http://127.0.0.1:${port}`;
5462
+ }
5463
+ function findFreePort() {
5464
+ return new Promise((resolve, reject) => {
5465
+ const srv = createServer();
5466
+ srv.on("error", reject);
5467
+ srv.listen(0, "127.0.0.1", () => {
5468
+ const addr = srv.address();
5469
+ if (addr && typeof addr === "object") {
5470
+ const { port } = addr;
5471
+ srv.close(() => resolve(port));
5472
+ } else {
5473
+ srv.close(() => reject(new Error("could not allocate a port")));
5474
+ }
5475
+ });
5476
+ });
5477
+ }
5478
+ async function startOpencodeSession(config2) {
5479
+ const { codeName, log: log2 } = config2;
5480
+ const existing = sessions.get(codeName);
5481
+ if (existing && existing.status === "running") return existing;
5482
+ const restartCount = existing?.restartCount ?? 0;
5483
+ if (existing?.status === "crashed" && existing.startedAt) {
5484
+ const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
5485
+ if (Date.now() - existing.startedAt < backoffMs) return existing;
5486
+ }
5487
+ if (config2.isolated) {
5488
+ log2(
5489
+ `[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.`
5490
+ );
5491
+ const blocked = {
5492
+ codeName,
5493
+ startedAt: Date.now(),
5494
+ restartCount: restartCount + 1,
5495
+ status: "crashed",
5496
+ port: null,
5497
+ password: null,
5498
+ lastFailureTail: "opencode isolation not implemented (ADR-0047)"
5499
+ };
5500
+ sessions.set(codeName, blocked);
5501
+ return blocked;
5502
+ }
5503
+ const session = {
5504
+ codeName,
5505
+ startedAt: null,
5506
+ restartCount,
5507
+ status: "starting",
5508
+ port: null,
5509
+ password: null,
5510
+ lastFailureTail: existing?.lastFailureTail ?? null
5511
+ };
5512
+ sessions.set(codeName, session);
5513
+ try {
5514
+ await spawnServe(config2, session);
5515
+ } catch (err) {
5516
+ log2(`[opencode-session] failed to start '${codeName}': ${err.message}`);
5517
+ session.status = "crashed";
5518
+ session.startedAt = Date.now();
5519
+ session.restartCount++;
5520
+ }
5521
+ return session;
5522
+ }
5523
+ async function spawnServe(config2, session) {
5524
+ const { codeName, projectDir, log: log2 } = config2;
5525
+ if (!existsSync9(join18(projectDir, "opencode.json"))) {
5526
+ log2(`[opencode-session] warning: no opencode.json in ${projectDir} for '${codeName}' (provisioning may not have run)`);
5527
+ }
5528
+ const tmuxSession = opencodeTmuxSession(codeName);
5529
+ const port = await findFreePort();
5530
+ const password = randomBytes(24).toString("base64url");
5531
+ try {
5532
+ execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
5533
+ } catch {
5534
+ }
5535
+ mkdirSync6(join18(homedir9(), ".augmented", codeName), { recursive: true });
5536
+ const serveEnv = {
5537
+ ...process.env,
5538
+ ...stripUndefined(config2.serveEnv ?? {}),
5539
+ OPENCODE_SERVER_PASSWORD: password,
5540
+ HOME: process.env.HOME?.trim() || homedir9(),
5541
+ USER: process.env.USER?.trim() || userInfo().username
5542
+ };
5543
+ if (config2.runId) serveEnv["AGT_RUN_ID"] = config2.runId;
5544
+ if (config2.agentTimezone) serveEnv["TZ"] = config2.agentTimezone;
5545
+ const serveCmd = `${OPENCODE_BIN} serve --hostname 127.0.0.1 --port ${port} --print-logs`;
5546
+ log2(`[opencode-session] starting '${tmuxSession}' for '${codeName}' on 127.0.0.1:${port}`);
5547
+ const child = spawn(
5548
+ "tmux",
5549
+ ["new-session", "-d", "-s", tmuxSession, "-c", projectDir, serveCmd],
5550
+ { cwd: projectDir, stdio: ["ignore", "pipe", "pipe"], env: serveEnv }
5551
+ );
5552
+ child.on("close", (code) => {
5553
+ if (code !== 0) {
5554
+ log2(`[opencode-session] failed to create tmux session for '${codeName}' (exit ${code})`);
5555
+ session.status = "crashed";
5556
+ session.startedAt = Date.now();
5557
+ session.restartCount++;
5558
+ return;
5559
+ }
5560
+ log2(`[opencode-session] tmux session '${tmuxSession}' created for '${codeName}'`);
5561
+ setupPaneLog(tmuxSession, codeName, log2);
5562
+ });
5563
+ child.on("error", (err) => {
5564
+ log2(`[opencode-session] failed to start tmux for '${codeName}': ${err.message}`);
5565
+ session.status = "crashed";
5566
+ session.startedAt = Date.now();
5567
+ session.restartCount++;
5568
+ });
5569
+ session.port = port;
5570
+ session.password = password;
5571
+ session.startedAt = Date.now();
5572
+ session.status = "running";
5573
+ session.restartCount = 0;
5574
+ bridges.delete(codeName);
5575
+ }
5576
+ function setupPaneLog(tmuxSession, codeName, log2) {
5577
+ const logPath = opencodePaneLogPath(codeName);
5578
+ try {
5579
+ execSync(`tmux pipe-pane -t ${tmuxSession} -o 'cat >> ${logPath}'`, { stdio: "ignore" });
5580
+ } catch (err) {
5581
+ log2(`[opencode-session] could not attach pane log for '${codeName}': ${err.message}`);
5582
+ }
5583
+ }
5584
+ function readOpencodePaneLogTail(codeName, lines = 40) {
5585
+ const logPath = opencodePaneLogPath(codeName);
5586
+ if (!existsSync9(logPath)) return null;
5587
+ try {
5588
+ const all = readFileSync15(logPath, "utf8").split("\n");
5589
+ return all.slice(-lines).join("\n");
5590
+ } catch {
5591
+ return null;
5592
+ }
5593
+ }
5594
+ function isOpencodeSessionHealthy(codeName) {
5595
+ const tmuxSession = opencodeTmuxSession(codeName);
5596
+ try {
5597
+ execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
5598
+ return true;
5599
+ } catch {
5600
+ return false;
5601
+ }
5602
+ }
5603
+ async function injectOpencodeMessage(codeName, msg, opts = {}) {
5604
+ const session = sessions.get(codeName);
5605
+ if (!session || session.status !== "running" || !session.port || !session.password) {
5606
+ return { status: "declined", reason: "server_not_running" };
5607
+ }
5608
+ const bridge = getBridge(codeName, session.port, session.password);
5609
+ return bridge.handleInbound(msg, { gate: opts.gate, awaitReply: opts.awaitReply });
5610
+ }
5611
+ function getBridge(codeName, port, password) {
5612
+ const cached = bridges.get(codeName);
5613
+ if (cached && cached.port === port && cached.password === password) return cached.bridge;
5614
+ const client2 = new HttpOpencodeClient({ baseUrl: baseUrlFor(port), password });
5615
+ const bridge = new OpencodeInboundBridge({ client: client2 });
5616
+ bridges.set(codeName, { port, password, bridge });
5617
+ return bridge;
5618
+ }
5619
+ function stripUndefined(env) {
5620
+ const out = {};
5621
+ for (const [k, v] of Object.entries(env)) if (v !== void 0) out[k] = v;
5622
+ return out;
5623
+ }
5624
+
5625
+ // src/lib/opencode-slack-ingest.ts
5626
+ import { createHash as createHash11 } from "crypto";
5627
+
5628
+ // ../../packages/core/dist/channels/slack-rich-text.js
5629
+ var MAX_DEPTH = 12;
5630
+ function isRecord(v) {
5631
+ return typeof v === "object" && v !== null && !Array.isArray(v);
5632
+ }
5633
+ function asString(v) {
5634
+ return typeof v === "string" ? v : "";
5635
+ }
5636
+ function asArray(v) {
5637
+ return Array.isArray(v) ? v : [];
5638
+ }
5639
+ function isTableElement(el) {
5640
+ return isRecord(el) && asString(el.type).includes("table");
5641
+ }
5642
+ function renderEmoji(el) {
5643
+ const unicode = asString(el.unicode);
5644
+ if (unicode) {
5645
+ try {
5646
+ const cps = unicode.split("-").map((h) => parseInt(h, 16));
5647
+ if (cps.every((n) => Number.isFinite(n) && n >= 0 && n <= 1114111)) {
5648
+ return String.fromCodePoint(...cps);
5649
+ }
5650
+ } catch {
5651
+ }
5652
+ }
5653
+ const name = asString(el.name);
5654
+ return name ? `:${name}:` : "";
5655
+ }
5656
+ function renderLeaf(el) {
5657
+ if (!isRecord(el))
5658
+ return "";
5659
+ switch (asString(el.type)) {
5660
+ case "text":
5661
+ return asString(el.text);
5662
+ case "link": {
5663
+ const label = asString(el.text);
5664
+ const url = asString(el.url);
5665
+ if (label && url && label !== url)
5666
+ return `${label} (${url})`;
5667
+ return label || url;
5668
+ }
5669
+ case "emoji":
5670
+ return renderEmoji(el);
5671
+ case "user":
5672
+ return `<@${asString(el.user_id)}>`;
5673
+ case "usergroup":
5674
+ return `<!subteam^${asString(el.usergroup_id)}>`;
5675
+ case "channel":
5676
+ return `<#${asString(el.channel_id)}>`;
5677
+ case "broadcast":
5678
+ return `@${asString(el.range) || "channel"}`;
5679
+ case "date":
5680
+ return asString(el.fallback) || asString(el.timestamp);
5681
+ default:
5682
+ if (Array.isArray(el.elements))
5683
+ return renderInline(el.elements);
5684
+ return asString(el.text);
5685
+ }
5686
+ }
5687
+ function renderInline(elements) {
5688
+ return elements.map(renderLeaf).join("");
5689
+ }
5690
+ function deepText(node, depth) {
5691
+ if (depth > MAX_DEPTH)
5692
+ return "";
5693
+ if (typeof node === "string")
5694
+ return node;
5695
+ if (Array.isArray(node))
5696
+ return node.map((n) => deepText(n, depth + 1)).join("");
5697
+ if (!isRecord(node))
5698
+ return "";
5699
+ const type = asString(node.type);
5700
+ if (type && type !== "rich_text" && !type.startsWith("rich_text_") && !type.includes("table")) {
5701
+ return renderLeaf(node);
5702
+ }
5703
+ if (Array.isArray(node.elements))
5704
+ return node.elements.map((n) => deepText(n, depth + 1)).join("");
5705
+ if (Array.isArray(node.rows))
5706
+ return node.rows.map((n) => deepText(n, depth + 1)).join(" ");
5707
+ return asString(node.text);
5708
+ }
5709
+ function renderCell(cell) {
5710
+ if (!isRecord(cell))
5711
+ return deepText(cell, 0).replace(/\s*\n\s*/g, " ").trim();
5712
+ const inner = Array.isArray(cell.elements) ? renderSection(cell, 0) : deepText(cell, 0);
5713
+ return inner.replace(/\s*\n\s*/g, " ").trim();
5714
+ }
5715
+ function renderTable(el) {
5716
+ const rawRows = asArray(el.elements).length ? asArray(el.elements) : asArray(el.rows);
5717
+ const rows = [];
5718
+ for (const row of rawRows) {
5719
+ if (!isRecord(row) && !Array.isArray(row))
5720
+ continue;
5721
+ const rawCells = Array.isArray(row) ? row : asArray(row.elements).length ? asArray(row.elements) : asArray(row.cells);
5722
+ if (rawCells.length === 0)
5723
+ continue;
5724
+ rows.push(rawCells.map(renderCell));
5725
+ }
5726
+ if (rows.length === 0) {
5727
+ const fallback = deepText(el, 0).replace(/[ \t]+\n/g, "\n").trim();
5728
+ return fallback;
5729
+ }
5730
+ const colCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
5731
+ const pad = (r) => {
5732
+ const cells = r.slice();
5733
+ while (cells.length < colCount)
5734
+ cells.push("");
5735
+ return cells.map((c) => c.replace(/\|/g, "\\|"));
5736
+ };
5737
+ const lines = [];
5738
+ const header = rows[0] ?? [];
5739
+ const body = rows.slice(1);
5740
+ lines.push(`| ${pad(header).join(" | ")} |`);
5741
+ lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
5742
+ for (const r of body)
5743
+ lines.push(`| ${pad(r).join(" | ")} |`);
5744
+ return lines.join("\n");
5745
+ }
5746
+ function renderSection(section, depth) {
5747
+ if (depth > MAX_DEPTH || !isRecord(section))
5748
+ return "";
5749
+ const type = asString(section.type);
5750
+ if (isTableElement(section))
5751
+ return renderTable(section);
5752
+ switch (type) {
5753
+ case "rich_text_section":
5754
+ return renderInline(asArray(section.elements));
5755
+ case "rich_text_preformatted": {
5756
+ const code = renderInline(asArray(section.elements));
5757
+ return code ? `\`\`\`
5758
+ ${code}
5759
+ \`\`\`` : "";
5760
+ }
5761
+ case "rich_text_quote": {
5762
+ const quoted = renderInline(asArray(section.elements));
5763
+ return quoted ? quoted.split("\n").map((l) => `> ${l}`).join("\n") : "";
5764
+ }
5765
+ case "rich_text_list": {
5766
+ const ordered = asString(section.style) === "ordered";
5767
+ const items = asArray(section.elements).map((item, i) => {
5768
+ const body = renderSection(item, depth + 1) || renderInline(asArray(item?.elements));
5769
+ const marker = ordered ? `${i + 1}.` : "-";
5770
+ return `${marker} ${body}`.trimEnd();
5771
+ });
5772
+ return items.join("\n");
5773
+ }
5774
+ default:
5775
+ if (Array.isArray(section.elements))
5776
+ return renderInline(asArray(section.elements));
5777
+ return deepText(section, depth + 1);
5778
+ }
5779
+ }
5780
+ function renderSlackBlocks(blocks) {
5781
+ let hadTable = false;
5782
+ const out = [];
5783
+ try {
5784
+ for (const block of asArray(blocks)) {
5785
+ if (!isRecord(block))
5786
+ continue;
5787
+ if (isTableElement(block)) {
5788
+ hadTable = true;
5789
+ const rendered = renderTable(block);
5790
+ if (rendered.trim().length > 0)
5791
+ out.push(rendered);
5792
+ continue;
5793
+ }
5794
+ if (asString(block.type) !== "rich_text")
5795
+ continue;
5796
+ for (const section of asArray(block.elements)) {
5797
+ if (isTableElement(section))
5798
+ hadTable = true;
5799
+ const rendered = renderSection(section, 0);
5800
+ if (rendered.trim().length > 0)
5801
+ out.push(rendered);
5802
+ }
5803
+ }
5804
+ } catch {
5805
+ }
5806
+ const text = out.join("\n\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
5807
+ return { text, hadTable };
5808
+ }
5809
+
5810
+ // ../../packages/core/dist/channels/governance/slack-forwarded.js
5811
+ function asTrimmed(v) {
5812
+ return typeof v === "string" ? v.trim() : "";
5813
+ }
5814
+ function extractForwardedContent(attachments) {
5815
+ if (!Array.isArray(attachments))
5816
+ return { text: "", files: [] };
5817
+ const parts = [];
5818
+ const files = [];
5819
+ for (const raw of attachments) {
5820
+ if (!raw || typeof raw !== "object")
5821
+ continue;
5822
+ const att = raw;
5823
+ const textBody = asTrimmed(att.text);
5824
+ let body = textBody || asTrimmed(att.fallback);
5825
+ const blockSources = [];
5826
+ if (Array.isArray(att.blocks))
5827
+ blockSources.push(att.blocks);
5828
+ if (Array.isArray(att.message_blocks)) {
5829
+ for (const mb of att.message_blocks)
5830
+ blockSources.push(mb?.message?.blocks);
5831
+ }
5832
+ for (const source of blockSources) {
5833
+ const rendered = renderSlackBlocks(source);
5834
+ const renderedText = rendered.text.trim();
5835
+ if (renderedText && (!textBody || rendered.hadTable)) {
5836
+ body = renderedText;
5837
+ break;
5838
+ }
5839
+ }
5840
+ const title = asTrimmed(att.title);
5841
+ const link = asTrimmed(att.title_link) || asTrimmed(att.from_url);
5842
+ const imageUrl = asTrimmed(att.image_url) || asTrimmed(att.thumb_url);
5843
+ const lines = [];
5844
+ if (body)
5845
+ lines.push(body);
5846
+ if (title && title !== body)
5847
+ lines.push(title);
5848
+ if (link)
5849
+ lines.push(link);
5850
+ if (imageUrl)
5851
+ lines.push(`[image] ${imageUrl}`);
5852
+ if (lines.length)
5853
+ parts.push(lines.join("\n"));
5854
+ if (Array.isArray(att.files))
5855
+ files.push(...att.files);
5856
+ }
5857
+ return { text: parts.join("\n\n"), files };
5858
+ }
5859
+ function hasForwardedContent(attachments) {
5860
+ const { text, files } = extractForwardedContent(attachments);
5861
+ return text.length > 0 || files.length > 0;
5862
+ }
5863
+
5864
+ // ../../packages/core/dist/channels/governance/slack-inbound-filter.js
5865
+ var ALLOWED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
5866
+ void 0,
5867
+ "file_share",
5868
+ "thread_broadcast"
5869
+ ]);
5870
+ function decideSlackMessageForward(evt) {
5871
+ if (evt.type !== "message") {
5872
+ return { forward: true };
5873
+ }
5874
+ if (!ALLOWED_MESSAGE_SUBTYPES.has(evt.subtype)) {
5875
+ return { forward: false, reason: "subtype" };
5876
+ }
5877
+ if (!evt.user) {
5878
+ return { forward: false, reason: "no_user" };
5879
+ }
5880
+ const hasText = typeof evt.text === "string" && evt.text.trim().length > 0;
5881
+ const hasFiles = Array.isArray(evt.files) && evt.files.length > 0;
5882
+ const hasBlocks = Array.isArray(evt.blocks) && evt.blocks.length > 0;
5883
+ const hasAttachments = hasForwardedContent(evt.attachments);
5884
+ if (!hasText && !hasFiles && !hasBlocks && !hasAttachments) {
5885
+ return { forward: false, reason: "empty" };
5886
+ }
5887
+ return { forward: true };
5888
+ }
5889
+ function isAppMentionEcho(evt, botUserId) {
5890
+ if (evt.type !== "message")
5891
+ return false;
5892
+ if (!botUserId)
5893
+ return false;
5894
+ if (evt.channel?.startsWith("D"))
5895
+ return false;
5896
+ return typeof evt.text === "string" && evt.text.includes(`<@${botUserId}>`);
5897
+ }
5898
+ function extractAugmentedSlackLabel(evt) {
5899
+ if (evt.metadata?.event_type !== "augmented_agent_message")
5900
+ return null;
5901
+ return {
5902
+ teamId: evt.metadata.event_payload?.["augmented_team_id"],
5903
+ agentId: evt.metadata.event_payload?.["augmented_agent_id"]
5904
+ };
5905
+ }
5906
+ function decideModeForward(evt, policy) {
5907
+ if (policy.mode === "all")
5908
+ return { forward: true };
5909
+ if (policy.mode === "manager_only") {
5910
+ if (policy.principalSlackUserId && evt.user === policy.principalSlackUserId) {
5911
+ return { forward: true };
5912
+ }
5913
+ }
5914
+ if (policy.mode === "team_only") {
5915
+ if (policy.teamPrincipalSlackUserIds && policy.teamPrincipalSlackUserIds.length > 0 && typeof evt.user === "string" && policy.teamPrincipalSlackUserIds.includes(evt.user)) {
5916
+ return { forward: true };
5917
+ }
5918
+ }
5919
+ const label = extractAugmentedSlackLabel(evt);
5920
+ if (!label)
5921
+ return { forward: false, reason: "sender_policy" };
5922
+ if (policy.mode === "team_agents_only" || policy.mode === "manager_only" || policy.mode === "team_only") {
5923
+ if (!policy.teamId || label.teamId !== policy.teamId) {
5924
+ return { forward: false, reason: "sender_policy" };
5925
+ }
5926
+ }
5927
+ return { forward: true };
5928
+ }
5929
+ function decideSenderPolicyForward(evt, policy) {
5930
+ if (policy.internalOnly) {
5931
+ if (!policy.homeTeamId)
5932
+ return { forward: false, reason: "sender_policy" };
5933
+ if (evt.team !== policy.homeTeamId)
5934
+ return { forward: false, reason: "sender_policy" };
5935
+ }
5936
+ return decideModeForward(evt, policy);
5937
+ }
5938
+
5939
+ // ../../packages/core/dist/channels/governance/slack-peer-classifier.js
5940
+ var CODE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
5941
+ function classifyPeerMessage(msg, cfg, self) {
5942
+ if (!msg.bot_id)
5943
+ return { kind: "human" };
5944
+ if (self.bot_user_id !== null && msg.user === self.bot_user_id) {
5945
+ return { kind: "self" };
5946
+ }
5947
+ if (cfg.peer_disabled_mode === "all") {
5948
+ return { kind: "drop", reason: "peer_disabled_all" };
5949
+ }
5950
+ if (cfg.peer_agent_mode === "off") {
5951
+ return { kind: "drop", reason: "mode_off" };
5952
+ }
5953
+ if (cfg.peer_group_ids.length === 0) {
5954
+ if (msg.channel_type !== "im") {
5955
+ return { kind: "drop", reason: "chat_not_allowed" };
5956
+ }
5957
+ } else if (!cfg.peer_group_ids.includes(msg.channel)) {
5958
+ return { kind: "drop", reason: "chat_not_allowed" };
5959
+ }
5960
+ const peer = cfg.peers.find((p) => p.bot_user_id === msg.user);
5961
+ if (!peer) {
5962
+ return { kind: "drop", reason: "unknown_peer" };
5963
+ }
5964
+ if (peer.gate_path === null) {
5965
+ return { kind: "drop", reason: "cross_team_grant_missing" };
5966
+ }
5967
+ if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
5968
+ return { kind: "drop", reason: "peer_disabled_cross_team" };
5969
+ }
5970
+ if (self.bot_user_id === null) {
5971
+ return { kind: "drop", reason: "self_resolution_pending" };
5972
+ }
5973
+ const addressing = classifyAddressing(msg, self.bot_user_id);
5974
+ if (!addressing.addressed) {
5975
+ return { kind: "drop", reason: "not_addressed" };
5976
+ }
5977
+ if (addressing.viaReplyOnly) {
5978
+ const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(msg.channel);
5979
+ const respondMode = cfg.peer_agent_mode === "respond";
5980
+ if (!(inAllowedGroup && respondMode)) {
5981
+ return { kind: "drop", reason: "not_addressed" };
5982
+ }
5983
+ }
5984
+ return { kind: "peer-ingress", peer };
5985
+ }
5986
+ function classifyAddressing(msg, ourBotUserId) {
5987
+ const text = msg.text ?? "";
5988
+ const mentionToken = `<@${ourBotUserId}>`;
5989
+ const viaMention = text.includes(mentionToken);
5990
+ const viaReply = !!msg.thread_ts && msg.thread_ts !== void 0 && msg.parent_user_id === ourBotUserId;
5991
+ if (viaMention) {
5992
+ return { addressed: true, viaReplyOnly: false };
5993
+ }
5994
+ if (viaReply) {
5995
+ return { addressed: true, viaReplyOnly: true };
5996
+ }
5997
+ return { addressed: false, viaReplyOnly: false };
5998
+ }
5999
+ function parsePeersEnv(raw, gateRaw) {
6000
+ if (!raw || raw.trim().length === 0)
6001
+ return [];
6002
+ let parsed;
6003
+ try {
6004
+ parsed = JSON.parse(raw);
6005
+ } catch {
6006
+ return [];
6007
+ }
6008
+ if (!Array.isArray(parsed))
6009
+ return [];
6010
+ const gateMap = /* @__PURE__ */ new Map();
6011
+ let gateConfigInvalid = false;
6012
+ if (gateRaw && gateRaw.trim().length > 0) {
6013
+ try {
6014
+ const gateParsed = JSON.parse(gateRaw);
6015
+ if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
6016
+ gateConfigInvalid = true;
6017
+ } else {
6018
+ for (const [k, v] of Object.entries(gateParsed)) {
6019
+ if (v === null) {
6020
+ gateMap.set(k, null);
6021
+ } else if (typeof v === "string") {
6022
+ const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
6023
+ if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
6024
+ gateMap.set(k, v);
6025
+ } else {
6026
+ gateConfigInvalid = true;
6027
+ break;
6028
+ }
6029
+ } else {
6030
+ gateConfigInvalid = true;
6031
+ break;
6032
+ }
6033
+ }
6034
+ }
6035
+ } catch {
6036
+ gateConfigInvalid = true;
6037
+ }
6038
+ if (gateConfigInvalid) {
6039
+ console.error("[slack-peer-classifier] SLACK_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)");
6040
+ }
6041
+ }
6042
+ const out = [];
6043
+ for (const entry of parsed) {
6044
+ 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) {
6045
+ const e = entry;
6046
+ const peer = {
6047
+ code_name: e.code_name,
6048
+ bot_user_id: e.bot_user_id,
6049
+ agent_id: e.agent_id
6050
+ };
6051
+ if (gateConfigInvalid) {
6052
+ peer.gate_path = null;
6053
+ } else if (gateMap.has(e.bot_user_id)) {
6054
+ peer.gate_path = gateMap.get(e.bot_user_id) ?? null;
6055
+ }
6056
+ out.push(peer);
6057
+ }
6058
+ }
6059
+ return out;
6060
+ }
6061
+ function parsePeerGroupIdsEnv(raw) {
6062
+ if (!raw)
6063
+ return [];
6064
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
6065
+ }
6066
+ function parsePeerAgentModeEnv(raw) {
6067
+ if (raw === "listen" || raw === "respond")
6068
+ return raw;
6069
+ return "off";
6070
+ }
6071
+
6072
+ // ../../packages/core/dist/channels/governance/sender-policy-decline.js
6073
+ function classifySlackPolicyBlock(evt, policy) {
6074
+ if (policy.internalOnly && (!policy.homeTeamId || evt.team !== policy.homeTeamId)) {
6075
+ return "internal_only";
6076
+ }
6077
+ switch (policy.mode) {
6078
+ case "manager_only":
6079
+ return "mode_manager_only";
6080
+ case "team_only":
6081
+ return "mode_team_only";
6082
+ case "team_agents_only":
6083
+ return "mode_team_agents_only";
6084
+ case "agents_only":
6085
+ return "mode_agents_only";
6086
+ case "all":
6087
+ return "internal_only";
6088
+ }
6089
+ }
6090
+ function politeDeclineCopy(reason) {
6091
+ switch (reason) {
6092
+ case "internal_only":
6093
+ return "Sorry, I can only respond to people inside our organisation. This conversation appears to be from outside.";
6094
+ case "mode_manager_only":
6095
+ return "Sorry, I only respond to my manager in this channel.";
6096
+ case "mode_team_only":
6097
+ return "Sorry, this agent only replies to its team members.";
6098
+ case "mode_team_agents_only":
6099
+ return "Sorry, I only respond to agents on my team in this channel.";
6100
+ case "mode_agents_only":
6101
+ return "Sorry, I only respond to other agents in this channel.";
6102
+ }
6103
+ }
6104
+
6105
+ // ../../packages/core/dist/channels/governance/inbound-access.js
6106
+ function decideInboundAccess(input) {
6107
+ if (!input.content.forward) {
6108
+ return { kind: "drop", reason: `content:${input.content.reason}` };
6109
+ }
6110
+ if (input.isSelf) {
6111
+ return { kind: "drop", reason: "self" };
6112
+ }
6113
+ if (input.orgBoundary && !input.orgBoundary.forward) {
6114
+ return { kind: "drop", reason: "org_boundary" };
6115
+ }
6116
+ if (input.senderPolicy && !input.senderPolicy.forward) {
6117
+ const reason = input.senderPolicy.reason;
6118
+ const declineWorthy = input.sameOrg && reason !== "internal_only";
6119
+ return {
6120
+ kind: "drop",
6121
+ reason: `sender_policy:${reason}`,
6122
+ decline: declineWorthy ? politeDeclineCopy(reason) : void 0
6123
+ };
6124
+ }
6125
+ if (input.command && !input.isBot) {
6126
+ return {
6127
+ kind: "command",
6128
+ command: input.command.command,
6129
+ authorized: input.command.authorized
6130
+ };
6131
+ }
6132
+ if (input.isBot) {
6133
+ const peer = input.peer;
6134
+ if (!peer || peer.kind === "self") {
6135
+ return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
6136
+ }
6137
+ if (peer.kind === "drop") {
6138
+ return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
6139
+ }
6140
+ }
6141
+ return { kind: "admit" };
6142
+ }
6143
+
6144
+ // ../../packages/core/dist/channels/governance/slack-sender-gate.js
6145
+ function evaluateSlackInboundGate(evt, deps) {
6146
+ const isBot = Boolean(evt.bot_id);
6147
+ const content = decideSlackMessageForward(evt);
6148
+ const spDecision = decideSenderPolicyForward(evt, deps.senderPolicy);
6149
+ const senderPolicy = spDecision.forward ? { forward: true } : { forward: false, reason: classifySlackPolicyBlock(evt, deps.senderPolicy) };
6150
+ const peer = isBot ? (() => {
6151
+ const c = classifyPeerMessage({
6152
+ text: evt.text,
6153
+ channel: evt.channel ?? "",
6154
+ channel_type: evt.channel_type,
6155
+ user: evt.user,
6156
+ bot_id: evt.bot_id,
6157
+ thread_ts: evt.thread_ts,
6158
+ parent_user_id: evt.parent_user_id
6159
+ }, deps.peerConfig, { bot_user_id: deps.botUserId ?? null });
6160
+ return { kind: c.kind, reason: c.kind === "drop" ? c.reason : void 0 };
6161
+ })() : void 0;
6162
+ const decision = decideInboundAccess({
6163
+ content,
6164
+ isSelf: Boolean(evt.user && deps.botUserId && evt.user === deps.botUserId),
6165
+ isBot,
6166
+ peer,
6167
+ senderPolicy,
6168
+ // Injection gate: no command dispatch and no same-org decline copy needed.
6169
+ sameOrg: false
6170
+ });
6171
+ if (decision.kind === "drop")
6172
+ return { admit: false, reason: decision.reason };
6173
+ return { admit: true };
6174
+ }
6175
+
6176
+ // ../../packages/core/dist/channels/governance/slack-inbound-config.js
6177
+ function buildSlackSenderPolicyFromEnv(env, ctx) {
6178
+ const raw = (env["SLACK_SENDER_POLICY"] ?? "all").trim().toLowerCase();
6179
+ const internalOnly = env["SLACK_INTERNAL_ONLY"] === "true";
6180
+ const homeTeamId = env["SLACK_HOME_TEAM_ID"];
6181
+ if (internalOnly && !homeTeamId) {
6182
+ 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).");
6183
+ }
6184
+ const internalOnlyFields = internalOnly ? { internalOnly: true, homeTeamId } : {};
6185
+ const teamId = ctx.agentTeamId;
6186
+ if (raw === "all")
6187
+ return { mode: "all", ...internalOnlyFields };
6188
+ if (raw === "agents_only")
6189
+ return { mode: "agents_only", ...internalOnlyFields };
6190
+ if (raw === "team_agents_only") {
6191
+ if (!teamId) {
6192
+ throw new Error("SLACK_SENDER_POLICY=team_agents_only requires AGT_TEAM_ID");
6193
+ }
6194
+ return { mode: "team_agents_only", teamId, ...internalOnlyFields };
6195
+ }
6196
+ if (raw === "team_only") {
6197
+ if (!teamId) {
6198
+ throw new Error("SLACK_SENDER_POLICY=team_only requires AGT_TEAM_ID");
6199
+ }
6200
+ const teamPrincipalSlackUserIds = (env["SLACK_SENDER_POLICY_TEAM_PRINCIPAL_IDS"] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6201
+ return { mode: "team_only", teamId, teamPrincipalSlackUserIds, ...internalOnlyFields };
6202
+ }
6203
+ if (raw === "manager_only") {
6204
+ const principalSlackUserId = env["SLACK_SENDER_POLICY_PRINCIPAL_ID"];
6205
+ if (!teamId) {
6206
+ throw new Error("SLACK_SENDER_POLICY=manager_only requires AGT_TEAM_ID");
6207
+ }
6208
+ if (!principalSlackUserId) {
6209
+ 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.");
6210
+ }
6211
+ return { mode: "manager_only", teamId, principalSlackUserId, ...internalOnlyFields };
6212
+ }
6213
+ throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(env["SLACK_SENDER_POLICY"])}`);
6214
+ }
6215
+ function parseSlackPeerDisabledModeEnv(env, onWarn) {
6216
+ const raw = (env["PEER_DISABLED"] ?? "").trim().toLowerCase();
6217
+ if (raw === "" || raw === "off")
6218
+ return "off";
6219
+ if (raw === "cross_team_only" || raw === "all")
6220
+ return raw;
6221
+ onWarn?.(`invalid PEER_DISABLED=${JSON.stringify(env["PEER_DISABLED"])} - defaulting to 'all' (fail-closed)`);
6222
+ return "all";
6223
+ }
6224
+ function buildSlackPeerClassifierConfigFromEnv(env, opts) {
6225
+ return {
6226
+ peer_agent_mode: parsePeerAgentModeEnv(env["SLACK_PEER_AGENT_MODE"]),
6227
+ peer_group_ids: parsePeerGroupIdsEnv(env["SLACK_PEER_GROUP_IDS"]),
6228
+ peers: parsePeersEnv(env["SLACK_PEERS"], env["SLACK_PEERS_GATE"]),
6229
+ peer_disabled_mode: parseSlackPeerDisabledModeEnv(env, opts?.onWarn)
6230
+ };
6231
+ }
6232
+
6233
+ // src/lib/slack-ingest.ts
6234
+ var SLACK_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
6235
+ "invalid_auth",
6236
+ "token_revoked",
6237
+ "token_expired",
6238
+ "account_inactive",
6239
+ "not_authed"
6240
+ ]);
6241
+ async function resolveSlackBotUserId(botToken, fetchImpl) {
6242
+ const doFetch = fetchImpl ?? globalThis.fetch;
6243
+ try {
6244
+ const res = await doFetch(`${SLACK_API}/auth.test`, {
6245
+ method: "POST",
6246
+ headers: { Authorization: `Bearer ${botToken}` },
6247
+ signal: AbortSignal.timeout(1e4)
6248
+ });
6249
+ const data = await res.json();
6250
+ if (data.ok) return { id: data.user_id, authFailed: false };
6251
+ return { id: void 0, authFailed: !!data.error && SLACK_AUTH_FAILURE_CODES.has(data.error) };
6252
+ } catch {
6253
+ return { id: void 0, authFailed: false };
6254
+ }
6255
+ }
6256
+ function resolveSlackIngestParamsFromEnv(env, ctx) {
6257
+ const appToken = env["SLACK_APP_TOKEN"];
6258
+ const botToken = env["SLACK_BOT_TOKEN"];
6259
+ if (!appToken || !botToken) return null;
6260
+ return {
6261
+ appToken,
6262
+ botToken,
6263
+ senderPolicy: buildSlackSenderPolicyFromEnv(env, ctx),
6264
+ peerConfig: buildSlackPeerClassifierConfigFromEnv(env)
6265
+ };
6266
+ }
6267
+ var BoundedSet = class {
6268
+ max;
6269
+ set = /* @__PURE__ */ new Set();
6270
+ constructor(max = 500) {
6271
+ this.max = max;
6272
+ }
6273
+ has(k) {
6274
+ return this.set.has(k);
6275
+ }
6276
+ add(k) {
6277
+ this.set.add(k);
6278
+ if (this.set.size > this.max) {
6279
+ const oldest = this.set.values().next().value;
6280
+ if (oldest !== void 0) this.set.delete(oldest);
6281
+ }
6282
+ }
6283
+ };
6284
+ async function dispatchSlackInbound(event, ctx) {
6285
+ if (isAppMentionEcho(event, ctx.botUserId ?? null)) {
6286
+ return "dropped_echo";
6287
+ }
6288
+ const dedupKey = event.channel && event.ts ? `${event.channel}:${event.ts}` : null;
6289
+ if (dedupKey) {
6290
+ if (ctx.seen.has(dedupKey)) return "dropped_duplicate";
6291
+ ctx.seen.add(dedupKey);
6292
+ }
6293
+ const decision = evaluateSlackInboundGate(event, {
6294
+ senderPolicy: ctx.senderPolicy,
6295
+ peerConfig: ctx.peerConfig,
6296
+ botUserId: ctx.botUserId
6297
+ });
6298
+ if (!decision.admit) {
6299
+ ctx.log(`[slack-ingest:${ctx.codeName}] dropped ${redact(event.channel)} reason=${decision.reason}`);
6300
+ return "dropped_gate";
6301
+ }
6302
+ const conversationKey = `${event.channel ?? "nochannel"}:${event.thread_ts ?? event.ts ?? ""}`;
6303
+ const meta = {
6304
+ source: "slack",
6305
+ lane: "conversational",
6306
+ requires_reply: "true"
6307
+ };
6308
+ if (event.thread_ts) meta["thread_ts"] = event.thread_ts;
6309
+ else if (event.ts) meta["thread_ts"] = event.ts;
6310
+ const result = await ctx.inject({
6311
+ channelId: "slack",
6312
+ conversationKey,
6313
+ senderId: event.user ?? "unknown",
6314
+ text: event.text ?? "",
6315
+ meta
6316
+ });
6317
+ if (result.status === "declined") {
6318
+ ctx.log(`[slack-ingest:${ctx.codeName}] inject declined reason=${result.reason}`);
6319
+ return "declined";
6320
+ }
6321
+ if (result.status === "replied" && result.reply && event.channel) {
6322
+ await ctx.sendReply({
6323
+ channel: event.channel,
6324
+ text: result.reply,
6325
+ threadTs: event.thread_ts ?? event.ts
6326
+ });
6327
+ return "admitted_replied";
6328
+ }
6329
+ return "admitted_no_reply";
6330
+ }
6331
+ function redact(id) {
6332
+ if (!id) return "?";
6333
+ return `${id.slice(0, 1)}***`;
6334
+ }
6335
+ var SLACK_API = "https://slack.com/api";
6336
+ function defaultSendReply(botToken, fetchImpl, log2) {
6337
+ return async ({ channel, text, threadTs }) => {
6338
+ try {
6339
+ const res = await fetchImpl(`${SLACK_API}/chat.postMessage`, {
6340
+ method: "POST",
6341
+ headers: {
6342
+ Authorization: `Bearer ${botToken}`,
6343
+ "Content-Type": "application/json; charset=utf-8"
6344
+ },
6345
+ body: JSON.stringify({ channel, text, ...threadTs ? { thread_ts: threadTs } : {} })
6346
+ });
6347
+ const data = await res.json();
6348
+ if (!data.ok) log2(`[slack-ingest] chat.postMessage failed: ${data.error}`);
6349
+ } catch (err) {
6350
+ log2(`[slack-ingest] chat.postMessage threw: ${err instanceof Error ? err.message : String(err)}`);
6351
+ }
6352
+ };
6353
+ }
6354
+ function startSlackIngest(config2) {
6355
+ const fetchImpl = config2.fetchImpl ?? globalThis.fetch;
6356
+ const socketFactory = config2.socketFactory ?? ((url) => {
6357
+ const Ctor = globalThis.WebSocket;
6358
+ if (!Ctor) throw new Error("global WebSocket is unavailable (Node 22+ required for opencode Slack ingest)");
6359
+ return new Ctor(url);
6360
+ });
6361
+ const sendReply = config2.sendReply ?? defaultSendReply(config2.botToken, fetchImpl, config2.log);
6362
+ const seen = new BoundedSet();
6363
+ let stopped = false;
6364
+ let ws = null;
6365
+ const ctx = {
6366
+ codeName: config2.codeName,
6367
+ botUserId: config2.botUserId,
6368
+ senderPolicy: config2.senderPolicy,
6369
+ peerConfig: config2.peerConfig,
6370
+ inject: config2.inject,
6371
+ sendReply,
6372
+ log: config2.log,
6373
+ seen
6374
+ };
6375
+ const scheduleReconnect = (delayMs) => {
6376
+ if (stopped) return;
6377
+ setTimeout(() => void connect(), delayMs).unref?.();
6378
+ };
6379
+ const connect = async () => {
6380
+ if (stopped) return;
6381
+ let url;
6382
+ try {
6383
+ const res = await fetchImpl(`${SLACK_API}/apps.connections.open`, {
6384
+ method: "POST",
6385
+ headers: { Authorization: `Bearer ${config2.appToken}` },
6386
+ // Bound the open call so a hung request can't wedge ingest without a
6387
+ // reconnect (Node 22+ required for opencode Slack ingest, so
6388
+ // AbortSignal.timeout is available).
6389
+ signal: AbortSignal.timeout(1e4)
6390
+ });
6391
+ const data = await res.json();
6392
+ if (!data.ok || !data.url) {
6393
+ config2.log(`[slack-ingest:${config2.codeName}] apps.connections.open failed: ${data.error}`);
6394
+ scheduleReconnect(1e4);
6395
+ return;
6396
+ }
6397
+ url = data.url;
6398
+ } catch (err) {
6399
+ config2.log(`[slack-ingest:${config2.codeName}] connect threw: ${err instanceof Error ? err.message : String(err)}`);
6400
+ scheduleReconnect(1e4);
6401
+ return;
6402
+ }
6403
+ if (stopped) return;
6404
+ try {
6405
+ ws = socketFactory(url);
6406
+ ws.onopen = () => config2.log(`[slack-ingest:${config2.codeName}] Socket Mode connected`);
6407
+ ws.onclose = () => {
6408
+ if (!stopped) scheduleReconnect(5e3);
6409
+ };
6410
+ ws.onerror = () => {
6411
+ };
6412
+ ws.onmessage = (ev) => {
6413
+ void onEnvelope(String(ev.data));
6414
+ };
6415
+ } catch (err) {
6416
+ config2.log(`[slack-ingest:${config2.codeName}] socket construction threw: ${err instanceof Error ? err.message : String(err)}`);
6417
+ scheduleReconnect(1e4);
6418
+ }
6419
+ };
6420
+ const onEnvelope = async (raw) => {
6421
+ let msg;
6422
+ try {
6423
+ msg = JSON.parse(raw);
6424
+ } catch {
6425
+ return;
6426
+ }
6427
+ if (msg.envelope_id && ws) {
6428
+ try {
6429
+ ws.send(JSON.stringify({ envelope_id: msg.envelope_id }));
6430
+ } catch {
6431
+ }
6432
+ }
6433
+ if (msg.type === "disconnect") {
6434
+ try {
6435
+ ws?.close();
6436
+ } catch {
6437
+ }
6438
+ return;
6439
+ }
6440
+ if (msg.type !== "events_api") return;
6441
+ const event = msg.payload?.event;
6442
+ if (!event || event.type !== "message" && event.type !== "app_mention") return;
6443
+ try {
6444
+ await dispatchSlackInbound(event, ctx);
6445
+ } catch (err) {
6446
+ config2.log(`[slack-ingest:${config2.codeName}] dispatch threw: ${err instanceof Error ? err.message : String(err)}`);
6447
+ }
6448
+ };
6449
+ void connect();
6450
+ return {
6451
+ stop() {
6452
+ stopped = true;
6453
+ try {
6454
+ ws?.close();
6455
+ } catch {
6456
+ }
6457
+ ws = null;
6458
+ }
6459
+ };
6460
+ }
6461
+
6462
+ // src/lib/opencode-slack-ingest.ts
6463
+ var ingests = /* @__PURE__ */ new Map();
6464
+ function fingerprint(params) {
6465
+ return createHash11("sha256").update(
6466
+ JSON.stringify({
6467
+ appToken: params.appToken,
6468
+ botToken: params.botToken,
6469
+ senderPolicy: params.senderPolicy,
6470
+ peerConfig: params.peerConfig
6471
+ })
6472
+ ).digest("hex");
6473
+ }
6474
+ async function ensureOpencodeSlackIngest(opts) {
6475
+ const { codeName, agentId, log: log2 } = opts;
6476
+ const readEnv = opts.readEnv ?? readChannelServerEnv;
6477
+ let params;
6478
+ let env;
6479
+ try {
6480
+ env = readEnv(codeName, "slack");
6481
+ if (!env) {
6482
+ stopOpencodeSlackIngest(codeName, log2);
6483
+ return;
6484
+ }
6485
+ params = resolveSlackIngestParamsFromEnv(env, { agentTeamId: env["AGT_TEAM_ID"] ?? null });
6486
+ } catch (err) {
6487
+ stopOpencodeSlackIngest(codeName, log2);
6488
+ log2(
6489
+ `[slack-ingest:${codeName}] not started: ${err instanceof Error ? err.message : String(err)}`
6490
+ );
6491
+ return;
6492
+ }
6493
+ if (!params) {
6494
+ stopOpencodeSlackIngest(codeName, log2);
6495
+ return;
6496
+ }
6497
+ const key = fingerprint(params);
6498
+ const existing = ingests.get(codeName);
6499
+ if (existing?.key === key) return;
6500
+ const { id: botUserId, authFailed } = await resolveSlackBotUserId(params.botToken, opts.fetchImpl);
6501
+ if (authFailed) {
6502
+ stopOpencodeSlackIngest(codeName, log2);
6503
+ log2(`[slack-ingest:${codeName}] not started: Slack bot token rejected (needs reconnect)`);
6504
+ return;
6505
+ }
6506
+ if (!botUserId) {
6507
+ log2(`[slack-ingest:${codeName}] bot user id unresolved; echo filtering degraded`);
6508
+ }
6509
+ if (existing) {
6510
+ existing.handle.stop();
6511
+ ingests.delete(codeName);
6512
+ log2(`[slack-ingest:${codeName}] config changed; restarting ingest`);
6513
+ }
6514
+ const handle = startSlackIngest({
6515
+ codeName,
6516
+ agentId,
6517
+ appToken: params.appToken,
6518
+ botToken: params.botToken,
6519
+ botUserId,
6520
+ senderPolicy: params.senderPolicy,
6521
+ peerConfig: params.peerConfig,
6522
+ inject: (msg) => injectOpencodeMessage(codeName, msg, { awaitReply: true }),
6523
+ log: log2,
6524
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
6525
+ ...opts.socketFactory ? { socketFactory: opts.socketFactory } : {}
6526
+ });
6527
+ ingests.set(codeName, { key, handle });
6528
+ log2(`[slack-ingest:${codeName}] ingest started (policy=${params.senderPolicy.mode})`);
6529
+ }
6530
+ function stopOpencodeSlackIngest(codeName, log2) {
6531
+ const entry = ingests.get(codeName);
6532
+ if (!entry) return;
6533
+ entry.handle.stop();
6534
+ ingests.delete(codeName);
6535
+ log2?.(`[slack-ingest:${codeName}] ingest stopped`);
6536
+ }
6537
+ function stopAllOpencodeSlackIngests(log2) {
6538
+ for (const codeName of [...ingests.keys()]) stopOpencodeSlackIngest(codeName, log2);
6539
+ }
6540
+
5441
6541
  // src/lib/wedge-detection.ts
5442
6542
  var DEFAULTS = {
5443
6543
  inboundWaitSeconds: 120,
@@ -5578,24 +6678,24 @@ function partitionActionableByPoison(actionable, states, config2) {
5578
6678
  }
5579
6679
 
5580
6680
  // 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";
6681
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync16, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
6682
+ import { homedir as homedir10 } from "os";
6683
+ import { join as join19 } from "path";
5584
6684
  import { randomUUID } from "crypto";
5585
6685
  function restartFlagsDir() {
5586
- return join18(homedir9(), ".augmented", "restart-flags");
6686
+ return join19(homedir10(), ".augmented", "restart-flags");
5587
6687
  }
5588
6688
  function flagPath(codeName) {
5589
- return join18(restartFlagsDir(), `${codeName}.flag`);
6689
+ return join19(restartFlagsDir(), `${codeName}.flag`);
5590
6690
  }
5591
6691
  function readRestartFlags() {
5592
6692
  const dir = restartFlagsDir();
5593
- if (!existsSync9(dir)) return [];
6693
+ if (!existsSync10(dir)) return [];
5594
6694
  const out = [];
5595
6695
  for (const entry of readdirSync4(dir)) {
5596
6696
  if (!entry.endsWith(".flag")) continue;
5597
6697
  try {
5598
- const raw = readFileSync15(join18(dir, entry), "utf8");
6698
+ const raw = readFileSync16(join19(dir, entry), "utf8");
5599
6699
  const parsed = JSON.parse(raw);
5600
6700
  if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
5601
6701
  parsed.codeName = entry.replace(/\.flag$/, "");
@@ -5613,7 +6713,7 @@ function readRestartFlags() {
5613
6713
  }
5614
6714
  function deleteRestartFlag(codeName) {
5615
6715
  const path = flagPath(codeName);
5616
- if (existsSync9(path)) {
6716
+ if (existsSync10(path)) {
5617
6717
  rmSync3(path, { force: true });
5618
6718
  }
5619
6719
  }
@@ -5959,10 +7059,10 @@ function startRealtimeConfig(config2) {
5959
7059
  filter: filterStr
5960
7060
  }, (payload) => {
5961
7061
  const agent = payload.new;
5962
- const fingerprint = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
7062
+ const fingerprint2 = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
5963
7063
  const prev = lastKnown.get(agent.agent_id);
5964
- if (prev === fingerprint) return;
5965
- lastKnown.set(agent.agent_id, fingerprint);
7064
+ if (prev === fingerprint2) return;
7065
+ lastKnown.set(agent.agent_id, fingerprint2);
5966
7066
  log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
5967
7067
  onConfigChange(agent);
5968
7068
  }).subscribe((status) => {
@@ -6836,15 +7936,15 @@ var runningChannelSecretHashes = /* @__PURE__ */ new Map();
6836
7936
  var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
6837
7937
  function projectMcpHash(_codeName, projectDir) {
6838
7938
  try {
6839
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
6840
- return createHash11("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
7939
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
7940
+ return createHash12("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
6841
7941
  } catch {
6842
7942
  return null;
6843
7943
  }
6844
7944
  }
6845
7945
  function projectMcpKeys(_codeName, projectDir) {
6846
7946
  try {
6847
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7947
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6848
7948
  const parsed = JSON.parse(raw);
6849
7949
  const servers = parsed.mcpServers;
6850
7950
  if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
@@ -6862,7 +7962,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
6862
7962
  else runningMcpServerKeys.delete(codeName);
6863
7963
  let launchStructure = null;
6864
7964
  try {
6865
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7965
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6866
7966
  launchStructure = managedMcpStructureHashFromFile(
6867
7967
  JSON.parse(raw),
6868
7968
  isManagedMcpServerKey
@@ -6962,7 +8062,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
6962
8062
  if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
6963
8063
  let mcpJsonForRebind = null;
6964
8064
  try {
6965
- mcpJsonForRebind = JSON.parse(readFileSync16(join19(projectDir, ".mcp.json"), "utf-8"));
8065
+ mcpJsonForRebind = JSON.parse(readFileSync17(join20(projectDir, ".mcp.json"), "utf-8"));
6966
8066
  } catch {
6967
8067
  mcpJsonForRebind = null;
6968
8068
  }
@@ -7091,7 +8191,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
7091
8191
  function projectChannelSecretHash(projectDir) {
7092
8192
  try {
7093
8193
  const entries = parseEnvIntegrations(
7094
- readFileSync16(join19(projectDir, ".env.integrations"), "utf-8")
8194
+ readFileSync17(join20(projectDir, ".env.integrations"), "utf-8")
7095
8195
  );
7096
8196
  return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
7097
8197
  } catch {
@@ -7153,6 +8253,7 @@ function resolveAgentFramework(codeName) {
7153
8253
  function clearAgentCaches(agentId, codeName) {
7154
8254
  const channelCacheMutated = clearAgentState(agentId, codeName);
7155
8255
  agentChannelTokens.delete(codeName);
8256
+ stopOpencodeSlackIngest(codeName, log);
7156
8257
  agentFrameworkCache.delete(codeName);
7157
8258
  kanbanBoardCache.delete(codeName);
7158
8259
  notifyBoardCache.delete(codeName);
@@ -7179,7 +8280,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
7179
8280
  var lastVersionCheckAt = 0;
7180
8281
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7181
8282
  var lastResponsivenessProbeAt = 0;
7182
- var agtCliVersion = true ? "0.28.319" : "dev";
8283
+ var agtCliVersion = true ? "0.28.320" : "dev";
7183
8284
  function resolveBrewPath(execFileSync2) {
7184
8285
  try {
7185
8286
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7192,7 +8293,7 @@ function resolveBrewPath(execFileSync2) {
7192
8293
  "/usr/local/bin/brew"
7193
8294
  ];
7194
8295
  for (const path of fallbacks) {
7195
- if (existsSync10(path)) return path;
8296
+ if (existsSync11(path)) return path;
7196
8297
  }
7197
8298
  return null;
7198
8299
  }
@@ -7202,7 +8303,7 @@ function claudeBinaryInstalled(execFileSync2) {
7202
8303
  "/opt/homebrew/bin/claude",
7203
8304
  "/usr/local/bin/claude"
7204
8305
  ];
7205
- if (canonical.some((path) => existsSync10(path))) return true;
8306
+ if (canonical.some((path) => existsSync11(path))) return true;
7206
8307
  try {
7207
8308
  execFileSync2("which", ["claude"], { timeout: 5e3 });
7208
8309
  return true;
@@ -7238,7 +8339,7 @@ async function ensureToolkitCli(toolkitSlug) {
7238
8339
  }
7239
8340
  const { binary, installer, package: pkg, script } = integration.cli_tool;
7240
8341
  const resolvedInstaller = installer ?? "manual";
7241
- const { execFileSync: execFileSync2, execSync } = await import("child_process");
8342
+ const { execFileSync: execFileSync2, execSync: execSync2 } = await import("child_process");
7242
8343
  try {
7243
8344
  execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
7244
8345
  toolkitCliEnsured.add(toolkitSlug);
@@ -7289,7 +8390,7 @@ async function ensureToolkitCli(toolkitSlug) {
7289
8390
  return;
7290
8391
  }
7291
8392
  log(`[toolkit-install] ${toolkitSlug}: running declared install script\u2026`);
7292
- execSync(script, { timeout: 18e4, stdio: "pipe" });
8393
+ execSync2(script, { timeout: 18e4, stdio: "pipe" });
7293
8394
  }
7294
8395
  } catch (err) {
7295
8396
  const e = err;
@@ -7313,8 +8414,8 @@ async function ensureToolkitCli(toolkitSlug) {
7313
8414
  }
7314
8415
  function runAsync(cmd, args, opts) {
7315
8416
  return new Promise((resolve, reject) => {
7316
- import("child_process").then(({ spawn }) => {
7317
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], cwd: opts.cwd });
8417
+ import("child_process").then(({ spawn: spawn2 }) => {
8418
+ const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"], cwd: opts.cwd });
7318
8419
  let stdout = "";
7319
8420
  let stderr = "";
7320
8421
  let settled = false;
@@ -7357,8 +8458,8 @@ function claudeManagedSettingsPath() {
7357
8458
  function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7358
8459
  try {
7359
8460
  let settings = {};
7360
- if (existsSync10(path)) {
7361
- const raw = readFileSync16(path, "utf-8").trim();
8461
+ if (existsSync11(path)) {
8462
+ const raw = readFileSync17(path, "utf-8").trim();
7362
8463
  if (raw) {
7363
8464
  let parsed;
7364
8465
  try {
@@ -7374,7 +8475,7 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7374
8475
  }
7375
8476
  if (settings.channelsEnabled === true) return "ok";
7376
8477
  settings.channelsEnabled = true;
7377
- mkdirSync7(dirname6(path), { recursive: true });
8478
+ mkdirSync8(dirname6(path), { recursive: true });
7378
8479
  writeFileSync8(path, `${JSON.stringify(settings, null, 2)}
7379
8480
  `);
7380
8481
  log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
@@ -7420,7 +8521,7 @@ async function ensureFrameworkBinary(frameworkId) {
7420
8521
  if (!process.env.PATH?.split(":").includes(brewBinDir)) {
7421
8522
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
7422
8523
  }
7423
- if (existsSync10("/home/linuxbrew/.linuxbrew/bin/claude")) {
8524
+ if (existsSync11("/home/linuxbrew/.linuxbrew/bin/claude")) {
7424
8525
  log("Claude Code installed successfully");
7425
8526
  } else {
7426
8527
  log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
@@ -7476,7 +8577,7 @@ ${r.stderr}`;
7476
8577
  }
7477
8578
  var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7478
8579
  function selfUpdateAppliedMarkerPath() {
7479
- return join19(homedir10(), ".augmented", ".last-self-update-applied");
8580
+ return join20(homedir11(), ".augmented", ".last-self-update-applied");
7480
8581
  }
7481
8582
  var selfUpdateUpToDateLogged = false;
7482
8583
  var restartAfterUpgrade = false;
@@ -7500,7 +8601,7 @@ async function checkAndUpdateCli(opts) {
7500
8601
  const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
7501
8602
  if (!isBrewFormula && !isNpmGlobal) return "noop";
7502
8603
  const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
7503
- const markerPath = join19(homedir10(), ".augmented", ".last-update-check");
8604
+ const markerPath = join20(homedir11(), ".augmented", ".last-update-check");
7504
8605
  if (!force) {
7505
8606
  try {
7506
8607
  const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
@@ -7778,13 +8879,13 @@ async function checkClaudeAuth() {
7778
8879
  }
7779
8880
  var evalEmptyMcpConfigPath = null;
7780
8881
  function ensureEvalEmptyMcpConfig() {
7781
- if (evalEmptyMcpConfigPath && existsSync10(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
7782
- const dir = join19(homedir10(), ".augmented");
8882
+ if (evalEmptyMcpConfigPath && existsSync11(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
8883
+ const dir = join20(homedir11(), ".augmented");
7783
8884
  try {
7784
- mkdirSync7(dir, { recursive: true });
8885
+ mkdirSync8(dir, { recursive: true });
7785
8886
  } catch {
7786
8887
  }
7787
- const p = join19(dir, ".eval-empty-mcp.json");
8888
+ const p = join20(dir, ".eval-empty-mcp.json");
7788
8889
  writeFileSync8(p, JSON.stringify({ mcpServers: {} }));
7789
8890
  evalEmptyMcpConfigPath = p;
7790
8891
  return p;
@@ -7810,7 +8911,7 @@ async function runEvalClaude(prompt, model) {
7810
8911
  ""
7811
8912
  ];
7812
8913
  const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), args, {
7813
- cwd: homedir10(),
8914
+ cwd: homedir11(),
7814
8915
  timeout: 12e4,
7815
8916
  stdin: "ignore",
7816
8917
  env: childEnv,
@@ -7876,10 +8977,10 @@ function resolveConversationEvalBackend() {
7876
8977
  return conversationEvalBackend;
7877
8978
  }
7878
8979
  function getStateFile() {
7879
- return join19(config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
8980
+ return join20(config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
7880
8981
  }
7881
8982
  function channelHashCacheDir() {
7882
- return config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
8983
+ return config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7883
8984
  }
7884
8985
  function loadChannelHashCache2() {
7885
8986
  loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
@@ -7907,7 +9008,7 @@ function removeSenderPolicyBaselineEntry(agentId) {
7907
9008
  var _channelQuarantineStore = null;
7908
9009
  function channelQuarantineStore() {
7909
9010
  if (!_channelQuarantineStore) {
7910
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9011
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7911
9012
  _channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
7912
9013
  }
7913
9014
  return _channelQuarantineStore;
@@ -7915,7 +9016,7 @@ function channelQuarantineStore() {
7915
9016
  var _hostFlagStore = null;
7916
9017
  function hostFlagStore() {
7917
9018
  if (!_hostFlagStore) {
7918
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9019
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7919
9020
  _hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
7920
9021
  }
7921
9022
  return _hostFlagStore;
@@ -7983,12 +9084,12 @@ function parseSkillFrontmatter(content) {
7983
9084
  }
7984
9085
  async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
7985
9086
  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");
9087
+ const skillsDir = join20(configDir, codeName, "project", ".claude", "skills");
9088
+ const claudeMdPath = join20(configDir, codeName, "project", "CLAUDE.md");
7988
9089
  if (!ex(skillsDir) || !ex(claudeMdPath)) return;
7989
9090
  const entries = [];
7990
9091
  for (const dir of readdirSync6(skillsDir).sort()) {
7991
- const skillFile = join19(skillsDir, dir, "SKILL.md");
9092
+ const skillFile = join20(skillsDir, dir, "SKILL.md");
7992
9093
  if (!ex(skillFile)) continue;
7993
9094
  try {
7994
9095
  const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
@@ -8051,7 +9152,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
8051
9152
  if (codeNames.length === 0) return;
8052
9153
  void (async () => {
8053
9154
  try {
8054
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
9155
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
8055
9156
  await api.post("/host/heartbeat", {
8056
9157
  host_id: hostId,
8057
9158
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -8149,7 +9250,7 @@ async function pollCycle() {
8149
9250
  }
8150
9251
  try {
8151
9252
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
8152
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
9253
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
8153
9254
  const diagCodeNames = [...agentState.persistentSessionAgents];
8154
9255
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
8155
9256
  let tailscaleHostname;
@@ -8170,8 +9271,8 @@ async function pollCycle() {
8170
9271
  }
8171
9272
  let osUsername;
8172
9273
  try {
8173
- const { userInfo } = await import("os");
8174
- osUsername = userInfo().username;
9274
+ const { userInfo: userInfo2 } = await import("os");
9275
+ osUsername = userInfo2().username;
8175
9276
  } catch {
8176
9277
  }
8177
9278
  let claudeAuth = null;
@@ -8179,7 +9280,7 @@ async function pollCycle() {
8179
9280
  claudeAuth = await detectClaudeAuth();
8180
9281
  } catch (err) {
8181
9282
  const errText = err instanceof Error ? err.message : String(err);
8182
- const errId = createHash11("sha256").update(errText).digest("hex").slice(0, 12);
9283
+ const errId = createHash12("sha256").update(errText).digest("hex").slice(0, 12);
8183
9284
  log(`Claude auth detection failed (error_id=${errId})`);
8184
9285
  }
8185
9286
  const hostHasClaudeCode = state6.agents.some(
@@ -8298,7 +9399,7 @@ async function pollCycle() {
8298
9399
  const {
8299
9400
  collectResponsivenessProbes,
8300
9401
  getResponsivenessIntervalMs
8301
- } = await import("../responsiveness-probe-YIESMTKL.js");
9402
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8302
9403
  const probeIntervalMs = getResponsivenessIntervalMs();
8303
9404
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
8304
9405
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -8330,7 +9431,7 @@ async function pollCycle() {
8330
9431
  collectResponsivenessProbes,
8331
9432
  livePendingInboundOldestAgeSeconds,
8332
9433
  parkPendingInbound
8333
- } = await import("../responsiveness-probe-YIESMTKL.js");
9434
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8334
9435
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
8335
9436
  const wedgeNow = /* @__PURE__ */ new Date();
8336
9437
  const liveAgents = agentState.persistentSessionAgents;
@@ -8419,13 +9520,13 @@ async function pollCycle() {
8419
9520
  );
8420
9521
  if (hostFlagStore().getBoolean("wedge-transient-notice")) {
8421
9522
  try {
8422
- const paneTail = readFileSync16(paneLogPath(codeName), "utf8").slice(-65536);
9523
+ const paneTail = readFileSync17(paneLogPath(codeName), "utf8").slice(-65536);
8423
9524
  const transient = detectTransientApiErrorInLog(paneTail);
8424
9525
  if (transient) {
8425
- const wedgeHome = join19(homedir10(), ".augmented", codeName);
8426
- if (existsSync10(wedgeHome)) {
9526
+ const wedgeHome = join20(homedir11(), ".augmented", codeName);
9527
+ if (existsSync11(wedgeHome)) {
8427
9528
  atomicWriteFileSync(
8428
- join19(wedgeHome, "watchdog-give-up.json"),
9529
+ join20(wedgeHome, "watchdog-give-up.json"),
8429
9530
  JSON.stringify({
8430
9531
  gave_up_at: wedgeNow.toISOString(),
8431
9532
  reason: "transient_overload"
@@ -8674,7 +9775,7 @@ async function pollCycle() {
8674
9775
  } catch {
8675
9776
  }
8676
9777
  killAgentChannelProcesses(prev.codeName, { log });
8677
- const agentDir = join19(adapter.getAgentDir(prev.codeName), "provision");
9778
+ const agentDir = join20(adapter.getAgentDir(prev.codeName), "provision");
8678
9779
  await cleanupAgentFiles(prev.codeName, agentDir);
8679
9780
  clearAgentCaches(prev.agentId, prev.codeName);
8680
9781
  }
@@ -8761,10 +9862,10 @@ async function pollCycle() {
8761
9862
  // pending-inbound marker. Best-effort: a write failure is logged by
8762
9863
  // the watchdog, never fails the poll cycle.
8763
9864
  signalGiveUp: (codeName) => {
8764
- const dir = join19(homedir10(), ".augmented", codeName);
8765
- if (!existsSync10(dir)) return;
9865
+ const dir = join20(homedir11(), ".augmented", codeName);
9866
+ if (!existsSync11(dir)) return;
8766
9867
  atomicWriteFileSync(
8767
- join19(dir, "watchdog-give-up.json"),
9868
+ join20(dir, "watchdog-give-up.json"),
8768
9869
  JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
8769
9870
  );
8770
9871
  }
@@ -8894,7 +9995,7 @@ async function processAgent(agent, agentStates) {
8894
9995
  }
8895
9996
  const now = (/* @__PURE__ */ new Date()).toISOString();
8896
9997
  const adapter = resolveAgentFramework(agent.code_name);
8897
- let agentDir = join19(adapter.getAgentDir(agent.code_name), "provision");
9998
+ let agentDir = join20(adapter.getAgentDir(agent.code_name), "provision");
8898
9999
  if (agent.status === "draft" || agent.status === "paused") {
8899
10000
  if (previousKnownStatus !== agent.status) {
8900
10001
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
@@ -8940,7 +10041,7 @@ async function processAgent(agent, agentStates) {
8940
10041
  const residuals = {
8941
10042
  gatewayRunning: false,
8942
10043
  portAllocated: false,
8943
- provisionDirExists: existsSync10(agentDir)
10044
+ provisionDirExists: existsSync11(agentDir)
8944
10045
  };
8945
10046
  if (!hasRevokedResiduals(residuals)) {
8946
10047
  agentStates.push({
@@ -9078,7 +10179,7 @@ async function processAgent(agent, agentStates) {
9078
10179
  const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
9079
10180
  agentFrameworkCache.set(agent.code_name, frameworkId);
9080
10181
  const frameworkAdapter = getFramework(frameworkId);
9081
- agentDir = join19(frameworkAdapter.getAgentDir(agent.code_name), "provision");
10182
+ agentDir = join20(frameworkAdapter.getAgentDir(agent.code_name), "provision");
9082
10183
  cacheAgentDeliveryMetadata(agent.code_name, refreshData);
9083
10184
  agentRestartTimezoneInputs.set(agent.code_name, {
9084
10185
  agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
@@ -9123,9 +10224,9 @@ async function processAgent(agent, agentStates) {
9123
10224
  try {
9124
10225
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
9125
10226
  const changedFiles = [];
9126
- mkdirSync7(agentDir, { recursive: true });
10227
+ mkdirSync8(agentDir, { recursive: true });
9127
10228
  for (const artifact of artifacts) {
9128
- const filePath = join19(agentDir, artifact.relativePath);
10229
+ const filePath = join20(agentDir, artifact.relativePath);
9129
10230
  let existingHash;
9130
10231
  let newHash;
9131
10232
  let writeContent = artifact.content;
@@ -9144,8 +10245,8 @@ async function processAgent(agent, agentStates) {
9144
10245
  };
9145
10246
  newHash = sha256(stripDynamicSections(artifact.content));
9146
10247
  try {
9147
- const projectClaudeMd = join19(config.configDir, agent.code_name, "project", "CLAUDE.md");
9148
- const existing = readFileSync16(projectClaudeMd, "utf-8");
10248
+ const projectClaudeMd = join20(config.configDir, agent.code_name, "project", "CLAUDE.md");
10249
+ const existing = readFileSync17(projectClaudeMd, "utf-8");
9149
10250
  existingHash = sha256(stripDynamicSections(existing));
9150
10251
  } catch {
9151
10252
  existingHash = null;
@@ -9163,7 +10264,7 @@ async function processAgent(agent, agentStates) {
9163
10264
  const generatorKeys = Object.keys(generatorServers);
9164
10265
  let existingRaw = "";
9165
10266
  try {
9166
- existingRaw = readFileSync16(filePath, "utf-8");
10267
+ existingRaw = readFileSync17(filePath, "utf-8");
9167
10268
  } catch {
9168
10269
  }
9169
10270
  const existingServers = parseMcp(existingRaw);
@@ -9185,13 +10286,13 @@ async function processAgent(agent, agentStates) {
9185
10286
  }
9186
10287
  }
9187
10288
  if (changedFiles.length > 0) {
9188
- const isFirst = !existsSync10(join19(agentDir, "CHARTER.md"));
10289
+ const isFirst = !existsSync11(join20(agentDir, "CHARTER.md"));
9189
10290
  const verb = isFirst ? "Provisioning" : "Updating";
9190
10291
  const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
9191
10292
  log(`${verb} '${agent.code_name}': ${fileNames}`);
9192
10293
  for (const file of changedFiles) {
9193
- const filePath = join19(agentDir, file.relativePath);
9194
- mkdirSync7(dirname6(filePath), { recursive: true });
10294
+ const filePath = join20(agentDir, file.relativePath);
10295
+ mkdirSync8(dirname6(filePath), { recursive: true });
9195
10296
  if (file.relativePath === ".mcp.json") {
9196
10297
  safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
9197
10298
  } else {
@@ -9199,12 +10300,12 @@ async function processAgent(agent, agentStates) {
9199
10300
  }
9200
10301
  }
9201
10302
  try {
9202
- const provSkillsDir = join19(agentDir, ".claude", "skills");
9203
- if (existsSync10(provSkillsDir)) {
10303
+ const provSkillsDir = join20(agentDir, ".claude", "skills");
10304
+ if (existsSync11(provSkillsDir)) {
9204
10305
  for (const folder of readdirSync5(provSkillsDir)) {
9205
10306
  if (folder.startsWith("knowledge-")) {
9206
10307
  try {
9207
- rmSync4(join19(provSkillsDir, folder), { recursive: true });
10308
+ rmSync4(join20(provSkillsDir, folder), { recursive: true });
9208
10309
  } catch {
9209
10310
  }
9210
10311
  }
@@ -9217,7 +10318,7 @@ async function processAgent(agent, agentStates) {
9217
10318
  const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
9218
10319
  const hashes = /* @__PURE__ */ new Map();
9219
10320
  for (const file of trackedFiles2) {
9220
- const h = hashFile(join19(agentDir, file));
10321
+ const h = hashFile(join20(agentDir, file));
9221
10322
  if (h) hashes.set(file, h);
9222
10323
  }
9223
10324
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -9235,14 +10336,14 @@ async function processAgent(agent, agentStates) {
9235
10336
  }
9236
10337
  if (Array.isArray(refreshData.workflows)) {
9237
10338
  try {
9238
- const provWorkflowsDir = join19(agentDir, ".claude", "workflows");
9239
- if (existsSync10(provWorkflowsDir)) {
10339
+ const provWorkflowsDir = join20(agentDir, ".claude", "workflows");
10340
+ if (existsSync11(provWorkflowsDir)) {
9240
10341
  const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
9241
10342
  for (const file of readdirSync5(provWorkflowsDir)) {
9242
10343
  if (!file.endsWith(".js")) continue;
9243
10344
  if (expected.has(file)) continue;
9244
10345
  try {
9245
- rmSync4(join19(provWorkflowsDir, file));
10346
+ rmSync4(join20(provWorkflowsDir, file));
9246
10347
  } catch {
9247
10348
  }
9248
10349
  }
@@ -9299,10 +10400,10 @@ async function processAgent(agent, agentStates) {
9299
10400
  }
9300
10401
  let lastDriftCheckAt = now;
9301
10402
  const written = agentState.writtenHashes.get(agent.agent_id);
9302
- if (written && existsSync10(agentDir)) {
10403
+ if (written && existsSync11(agentDir)) {
9303
10404
  const driftedFiles = [];
9304
10405
  for (const [file, expectedHash] of written) {
9305
- const localHash = hashFile(join19(agentDir, file));
10406
+ const localHash = hashFile(join20(agentDir, file));
9306
10407
  if (localHash && localHash !== expectedHash) {
9307
10408
  driftedFiles.push(file);
9308
10409
  }
@@ -9313,7 +10414,7 @@ async function processAgent(agent, agentStates) {
9313
10414
  try {
9314
10415
  const localHashes = {};
9315
10416
  for (const file of driftedFiles) {
9316
- localHashes[file] = hashFile(join19(agentDir, file));
10417
+ localHashes[file] = hashFile(join20(agentDir, file));
9317
10418
  }
9318
10419
  await api.post("/host/drift", {
9319
10420
  agent_id: agent.agent_id,
@@ -9500,15 +10601,15 @@ async function processAgent(agent, agentStates) {
9500
10601
  const addedChannels = [...restartDecision.added];
9501
10602
  const writeDmNoticeMarkers = isChannelAddRestart ? () => {
9502
10603
  try {
9503
- const agentAugmentedDir = join19(homedir10(), ".augmented", agent.code_name);
9504
- mkdirSync7(agentAugmentedDir, { recursive: true });
10604
+ const agentAugmentedDir = join20(homedir11(), ".augmented", agent.code_name);
10605
+ mkdirSync8(agentAugmentedDir, { recursive: true });
9505
10606
  const markerJson = JSON.stringify({
9506
10607
  version: 1,
9507
10608
  at: (/* @__PURE__ */ new Date()).toISOString(),
9508
10609
  added: addedChannels
9509
10610
  });
9510
10611
  for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
9511
- atomicWriteFileSync(join19(agentAugmentedDir, file), markerJson);
10612
+ atomicWriteFileSync(join20(agentAugmentedDir, file), markerJson);
9512
10613
  }
9513
10614
  } catch (err) {
9514
10615
  log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
@@ -9584,7 +10685,7 @@ async function processAgent(agent, agentStates) {
9584
10685
  const behaviourSubset = extractMsTeamsBehaviourSubset(
9585
10686
  msteamsEntry?.config
9586
10687
  );
9587
- const behaviourHash = createHash11("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
10688
+ const behaviourHash = createHash12("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
9588
10689
  const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
9589
10690
  const behaviourDecision = decideSenderPolicyRestart({
9590
10691
  previousHash: prevBehaviourHash,
@@ -9637,7 +10738,7 @@ async function processAgent(agent, agentStates) {
9637
10738
  const slackBehaviourSubset = extractSlackBehaviourSubset(
9638
10739
  slackEntry?.config
9639
10740
  );
9640
- const slackBehaviourHash = createHash11("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
10741
+ const slackBehaviourHash = createHash12("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
9641
10742
  const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
9642
10743
  const slackBehaviourDecision = decideSenderPolicyRestart({
9643
10744
  previousHash: prevSlackBehaviourHash,
@@ -9687,24 +10788,24 @@ async function processAgent(agent, agentStates) {
9687
10788
  if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
9688
10789
  try {
9689
10790
  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");
10791
+ const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
10792
+ mkdirSync8(agentProvisionDir, { recursive: true });
10793
+ mkdirSync8(projectDir, { recursive: true });
10794
+ const provisionMcpPath = join20(agentProvisionDir, ".mcp.json");
10795
+ const projectMcpPath = join20(projectDir, ".mcp.json");
9695
10796
  let mcpConfig = { mcpServers: {} };
9696
10797
  try {
9697
- mcpConfig = JSON.parse(readFileSync16(provisionMcpPath, "utf-8"));
10798
+ mcpConfig = JSON.parse(readFileSync17(provisionMcpPath, "utf-8"));
9698
10799
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
9699
10800
  } catch {
9700
10801
  }
9701
- const localDirectChatChannel = join19(homedir10(), ".augmented", "_mcp", "direct-chat-channel.js");
10802
+ const localDirectChatChannel = join20(homedir11(), ".augmented", "_mcp", "direct-chat-channel.js");
9702
10803
  const directChatTeamSettings = refreshData.team?.settings;
9703
10804
  const directChatTz = (() => {
9704
10805
  const tz = directChatTeamSettings?.["timezone"];
9705
10806
  return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
9706
10807
  })();
9707
- if (existsSync10(localDirectChatChannel)) {
10808
+ if (existsSync11(localDirectChatChannel)) {
9708
10809
  const directChatEnv = {
9709
10810
  AGT_HOST: requireHost(),
9710
10811
  // ENG-5901 Track D: templated — the manager exports the real
@@ -9724,7 +10825,7 @@ async function processAgent(agent, agentStates) {
9724
10825
  // ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
9725
10826
  // returns the agent root (NOT the /provision subdir `agentDir` points at),
9726
10827
  // so it byte-matches the broker readers' path.
9727
- AGT_TURN_INITIATOR_FILE: join19(
10828
+ AGT_TURN_INITIATOR_FILE: join20(
9728
10829
  frameworkAdapter.getAgentDir(agent.code_name),
9729
10830
  ".current-turn-initiator.json"
9730
10831
  )
@@ -9744,8 +10845,8 @@ async function processAgent(agent, agentStates) {
9744
10845
  log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
9745
10846
  }
9746
10847
  }
9747
- const staleChannelsPath = join19(projectDir, ".mcp-channels.json");
9748
- if (existsSync10(staleChannelsPath)) {
10848
+ const staleChannelsPath = join20(projectDir, ".mcp-channels.json");
10849
+ if (existsSync11(staleChannelsPath)) {
9749
10850
  try {
9750
10851
  rmSync4(staleChannelsPath, { force: true });
9751
10852
  } catch {
@@ -9834,7 +10935,7 @@ async function processAgent(agent, agentStates) {
9834
10935
  }
9835
10936
  if (hostFlagStore().getBoolean("connectivity-probe")) {
9836
10937
  try {
9837
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
10938
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9838
10939
  await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
9839
10940
  } catch (err) {
9840
10941
  log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
@@ -9845,7 +10946,7 @@ async function processAgent(agent, agentStates) {
9845
10946
  const forceDue = attemptsLeft > 0;
9846
10947
  let probeRan = false;
9847
10948
  try {
9848
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
10949
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9849
10950
  probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
9850
10951
  } catch (err) {
9851
10952
  log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
@@ -9858,9 +10959,9 @@ async function processAgent(agent, agentStates) {
9858
10959
  }
9859
10960
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
9860
10961
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
9861
- if (mcpPath && existsSync10(mcpPath)) {
10962
+ if (mcpPath && existsSync11(mcpPath)) {
9862
10963
  try {
9863
- const cfg = JSON.parse(readFileSync16(mcpPath, "utf-8"));
10964
+ const cfg = JSON.parse(readFileSync17(mcpPath, "utf-8"));
9864
10965
  const expectedRemoteKeys = new Set(
9865
10966
  integrations.map((i) => i.definition_id)
9866
10967
  );
@@ -9885,11 +10986,11 @@ async function processAgent(agent, agentStates) {
9885
10986
  recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
9886
10987
  }
9887
10988
  if (intHash !== prevIntHash) {
9888
- const projectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
9889
- const envIntPath = join19(projectDir, ".env.integrations");
10989
+ const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
10990
+ const envIntPath = join20(projectDir, ".env.integrations");
9890
10991
  let preWriteEnv;
9891
10992
  try {
9892
- preWriteEnv = readFileSync16(envIntPath, "utf-8");
10993
+ preWriteEnv = readFileSync17(envIntPath, "utf-8");
9893
10994
  } catch {
9894
10995
  preWriteEnv = void 0;
9895
10996
  }
@@ -9901,9 +11002,9 @@ async function processAgent(agent, agentStates) {
9901
11002
  let rotationHandled = true;
9902
11003
  if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
9903
11004
  try {
9904
- const projectMcpPath = join19(projectDir, ".mcp.json");
9905
- const postWriteEnv = readFileSync16(envIntPath, "utf-8");
9906
- const mcpContent = readFileSync16(projectMcpPath, "utf-8");
11005
+ const projectMcpPath = join20(projectDir, ".mcp.json");
11006
+ const postWriteEnv = readFileSync17(envIntPath, "utf-8");
11007
+ const mcpContent = readFileSync17(projectMcpPath, "utf-8");
9907
11008
  const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
9908
11009
  const mcpJsonForReap = JSON.parse(mcpContent);
9909
11010
  const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
@@ -9980,10 +11081,10 @@ async function processAgent(agent, agentStates) {
9980
11081
  desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
9981
11082
  }
9982
11083
  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);
11084
+ const headersHash = createHash12("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
9984
11085
  return `${e.serverId}|${e.url}|${headersHash}`;
9985
11086
  }).join("\n");
9986
- const mcpHash = createHash11("sha256").update(hashBasis).digest("hex").slice(0, 16);
11087
+ const mcpHash = createHash12("sha256").update(hashBasis).digest("hex").slice(0, 16);
9987
11088
  const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
9988
11089
  const structureHash = managedMcpStructureHash(desiredEntries);
9989
11090
  const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
@@ -9998,15 +11099,15 @@ async function processAgent(agent, agentStates) {
9998
11099
  if (mcpHash !== prevMcpHash) {
9999
11100
  for (const e of desiredEntries) {
10000
11101
  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);
11102
+ const urlHash = createHash12("sha256").update(e.url).digest("hex").slice(0, 12);
10002
11103
  log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
10003
11104
  }
10004
11105
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
10005
11106
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
10006
11107
  if (mcpPath) {
10007
11108
  try {
10008
- const { readFileSync: readFileSync17 } = await import("fs");
10009
- const mcpConfig = JSON.parse(readFileSync17(mcpPath, "utf-8"));
11109
+ const { readFileSync: readFileSync18 } = await import("fs");
11110
+ const mcpConfig = JSON.parse(readFileSync18(mcpPath, "utf-8"));
10010
11111
  if (mcpConfig.mcpServers) {
10011
11112
  for (const key of Object.keys(mcpConfig.mcpServers)) {
10012
11113
  if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
@@ -10107,7 +11208,7 @@ async function processAgent(agent, agentStates) {
10107
11208
  if (frameworkAdapter.installSkillFiles) {
10108
11209
  const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
10109
11210
  const installedIntegrationSkills = [];
10110
- const { createHash: createHash12 } = await import("crypto");
11211
+ const { createHash: createHash13 } = await import("crypto");
10111
11212
  const refreshAny = refreshData;
10112
11213
  const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
10113
11214
  const contextBySlug = /* @__PURE__ */ new Map();
@@ -10136,7 +11237,7 @@ async function processAgent(agent, agentStates) {
10136
11237
  )
10137
11238
  }));
10138
11239
  const bundle = buildIntegrationBundle(renderedScopes);
10139
- const contentHash = createHash12("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
11240
+ const contentHash = createHash13("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
10140
11241
  const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
10141
11242
  if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
10142
11243
  frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
@@ -10149,18 +11250,18 @@ async function processAgent(agent, agentStates) {
10149
11250
  }
10150
11251
  try {
10151
11252
  const { readdirSync: readdirSync6, rmSync: rmSync5 } = await import("fs");
10152
- const { homedir: homedir11 } = await import("os");
11253
+ const { homedir: homedir12 } = await import("os");
10153
11254
  const frameworkId2 = frameworkAdapter.id;
10154
11255
  const candidateSkillDirs = [
10155
11256
  // Claude Code — framework runtime tree
10156
- join19(homedir11(), ".augmented", agent.code_name, "skills"),
11257
+ join20(homedir12(), ".augmented", agent.code_name, "skills"),
10157
11258
  // Claude Code — project tree
10158
- join19(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
11259
+ join20(homedir12(), ".augmented", agent.code_name, "project", ".claude", "skills"),
10159
11260
  // Defensive: legacy provision-side path, not currently an
10160
11261
  // install target but cheap to sweep.
10161
- join19(agentDir, ".claude", "skills")
11262
+ join20(agentDir, ".claude", "skills")
10162
11263
  ];
10163
- const existingDirs = candidateSkillDirs.filter((d) => existsSync10(d));
11264
+ const existingDirs = candidateSkillDirs.filter((d) => existsSync11(d));
10164
11265
  const discoveredEntries = /* @__PURE__ */ new Set();
10165
11266
  for (const dir of existingDirs) {
10166
11267
  try {
@@ -10174,8 +11275,8 @@ async function processAgent(agent, agentStates) {
10174
11275
  }
10175
11276
  const removeSkillFolder = (entry, reason) => {
10176
11277
  for (const dir of existingDirs) {
10177
- const p = join19(dir, entry);
10178
- if (existsSync10(p)) {
11278
+ const p = join20(dir, entry);
11279
+ if (existsSync11(p)) {
10179
11280
  rmSync5(p, { recursive: true, force: true });
10180
11281
  }
10181
11282
  }
@@ -10194,7 +11295,7 @@ async function processAgent(agent, agentStates) {
10194
11295
  const sharedSkillsPayload = refreshAny.shared_skills;
10195
11296
  const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
10196
11297
  const manifestPath = managedSkillManifestPath(
10197
- join19(homedir10(), ".augmented", agent.code_name)
11298
+ join20(homedir11(), ".augmented", agent.code_name)
10198
11299
  );
10199
11300
  const prevIds = /* @__PURE__ */ new Set([
10200
11301
  ...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
@@ -10203,7 +11304,7 @@ async function processAgent(agent, agentStates) {
10203
11304
  const plan = planGlobalSkillSync(
10204
11305
  [...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
10205
11306
  prevIds,
10206
- (content) => createHash12("sha256").update(content).digest("hex").slice(0, 12),
11307
+ (content) => createHash13("sha256").update(content).digest("hex").slice(0, 12),
10207
11308
  (skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
10208
11309
  { desiredResolved }
10209
11310
  );
@@ -10214,15 +11315,15 @@ async function processAgent(agent, agentStates) {
10214
11315
  }
10215
11316
  if (plan.removes.length) {
10216
11317
  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")
11318
+ join20(homedir11(), ".augmented", agent.code_name, "skills"),
11319
+ join20(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
11320
+ join20(agentDir, ".claude", "skills")
10220
11321
  ];
10221
11322
  for (const id of plan.removes) {
10222
11323
  let prunedAny = false;
10223
11324
  for (const dir of globalSkillDirs) {
10224
- const p = join19(dir, id);
10225
- if (existsSync10(p) && existsSync10(join19(p, "SKILL.md"))) {
11325
+ const p = join20(dir, id);
11326
+ if (existsSync11(p) && existsSync11(join20(p, "SKILL.md"))) {
10226
11327
  rmSync4(p, { recursive: true, force: true });
10227
11328
  prunedAny = true;
10228
11329
  }
@@ -10254,7 +11355,7 @@ async function processAgent(agent, agentStates) {
10254
11355
  const slug = hook.integration_slug ?? hook.plugin_slug;
10255
11356
  if (!slug) continue;
10256
11357
  try {
10257
- const scriptHash = createHash12("sha256").update(hook.script).digest("hex").slice(0, 12);
11358
+ const scriptHash = createHash13("sha256").update(hook.script).digest("hex").slice(0, 12);
10258
11359
  const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
10259
11360
  if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
10260
11361
  const result = await frameworkAdapter.executePluginHook({
@@ -10269,9 +11370,9 @@ async function processAgent(agent, agentStates) {
10269
11370
  } else if (result.timedOut) {
10270
11371
  log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
10271
11372
  } else {
10272
- const stderrHash = createHash12("sha256").update(result.stderr).digest("hex").slice(0, 12);
11373
+ const stderrHash = createHash13("sha256").update(result.stderr).digest("hex").slice(0, 12);
10273
11374
  const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
10274
- const missingCmdHash = missingCmd ? createHash12("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
11375
+ const missingCmdHash = missingCmd ? createHash13("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
10275
11376
  log(
10276
11377
  `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
11378
  );
@@ -10425,8 +11526,8 @@ async function processAgent(agent, agentStates) {
10425
11526
  const sess = getSessionState(agent.code_name);
10426
11527
  let mcpJsonParsed = null;
10427
11528
  try {
10428
- const mcpPath = join19(getProjectDir(agent.code_name), ".mcp.json");
10429
- mcpJsonParsed = JSON.parse(readFileSync16(mcpPath, "utf-8"));
11529
+ const mcpPath = join20(getProjectDir(agent.code_name), ".mcp.json");
11530
+ mcpJsonParsed = JSON.parse(readFileSync17(mcpPath, "utf-8"));
10430
11531
  } catch {
10431
11532
  }
10432
11533
  reapMissingMcpSessions({
@@ -10751,10 +11852,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10751
11852
  }
10752
11853
  }
10753
11854
  const trackedFiles = frameworkAdapter.driftTrackedFiles();
10754
- if (trackedFiles.length > 0 && existsSync10(agentDir)) {
11855
+ if (trackedFiles.length > 0 && existsSync11(agentDir)) {
10755
11856
  const hashes = /* @__PURE__ */ new Map();
10756
11857
  for (const file of trackedFiles) {
10757
- const h = hashFile(join19(agentDir, file));
11858
+ const h = hashFile(join20(agentDir, file));
10758
11859
  if (h) hashes.set(file, h);
10759
11860
  }
10760
11861
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -10769,7 +11870,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10769
11870
  refreshData.agent.onboarding_state
10770
11871
  );
10771
11872
  const obStep = obState.step;
10772
- const markerPath = join19(homedir10(), ".augmented", agent.code_name, "onboarding-drive.json");
11873
+ const markerPath = join20(homedir11(), ".augmented", agent.code_name, "onboarding-drive.json");
10773
11874
  const marker = readOnboardingDriveMarker(markerPath);
10774
11875
  const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
10775
11876
  if (decision.clearMarker) {
@@ -10830,11 +11931,55 @@ function deriveEgressAllowlist(toolsRaw) {
10830
11931
  );
10831
11932
  }
10832
11933
  var egressAllowlistEqual = (a, b) => a.length === b.length && a.every((d, i) => d === b[i]);
11934
+ async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
11935
+ const codeName = agent.code_name;
11936
+ if (isOpencodeSessionHealthy(codeName)) {
11937
+ await ensureOpencodeSlackIngest({ codeName, agentId: agent.agent_id, log });
11938
+ return { decision: "healthy", spawnAttempted: false, sessionHealthyAfter: true };
11939
+ }
11940
+ stopOpencodeSlackIngest(codeName, log);
11941
+ const opencodeProjectDir = join20(getFramework("opencode").getAgentDir(codeName), "project");
11942
+ const openRouterRaw = refreshData.openrouter ?? null;
11943
+ const serveEnv = {
11944
+ AGT_HOST: requireHost(),
11945
+ AGT_API_KEY: getApiKey() ?? void 0,
11946
+ AGT_APP_URL: process.env["AGT_APP_URL"]
11947
+ };
11948
+ if (openRouterRaw?.auth_token) serveEnv["OPENROUTER_API_KEY"] = openRouterRaw.auth_token;
11949
+ const session = await startOpencodeSession({
11950
+ codeName,
11951
+ agentId: agent.agent_id,
11952
+ projectDir: opencodeProjectDir,
11953
+ serveEnv,
11954
+ agentTimezone,
11955
+ // ADR-0047: the opencode container launcher is not built, so the launcher
11956
+ // refuses to spawn under Docker isolation rather than run unisolated on a
11957
+ // multi-tenant host. Single-agent / dedicated hosts (isolation off) run bare.
11958
+ isolated: isolationMode(codeName) === "docker",
11959
+ log
11960
+ });
11961
+ agentState.persistentSessionAgents.add(codeName);
11962
+ if (session.status === "crashed") {
11963
+ const tail = session.lastFailureTail ?? readOpencodePaneLogTail(codeName);
11964
+ return {
11965
+ decision: "spawn",
11966
+ spawnAttempted: true,
11967
+ sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
11968
+ detail: tail ? `opencode spawn failed: ${tail.slice(0, 200)}` : "opencode spawn failed"
11969
+ };
11970
+ }
11971
+ return {
11972
+ decision: "spawn",
11973
+ spawnAttempted: true,
11974
+ sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
11975
+ detail: `opencode serve on 127.0.0.1:${session.port ?? "?"}`
11976
+ };
11977
+ }
10833
11978
  async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10834
11979
  const codeName = agent.code_name;
10835
11980
  const projectDir = getProjectDir(codeName);
10836
- const mcpConfigPath = join19(projectDir, ".mcp.json");
10837
- const claudeMdPath = join19(projectDir, "CLAUDE.md");
11981
+ const mcpConfigPath = join20(projectDir, ".mcp.json");
11982
+ const claudeMdPath = join20(projectDir, "CLAUDE.md");
10838
11983
  if (restartBreaker.isTripped(codeName)) {
10839
11984
  const trip = restartBreaker.getTrip(codeName);
10840
11985
  return {
@@ -10856,6 +12001,10 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10856
12001
  }
10857
12002
  return resolveAgentTimezone(ownTz, teamTz, orgTierTz);
10858
12003
  })();
12004
+ const frameworkId = agentFrameworkCache.get(codeName) ?? DEFAULT_FRAMEWORK;
12005
+ if (frameworkId === "opencode") {
12006
+ return ensureOpencodeRuntime(agent, refreshData, agentTimezone ?? null);
12007
+ }
10859
12008
  const channelConfigs = refreshData.channel_configs;
10860
12009
  const { devChannels, pluginChannels } = resolveChannelLaunchFlags(
10861
12010
  channelConfigs,
@@ -10988,7 +12137,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10988
12137
  const ctx = getLastFailureContext(codeName);
10989
12138
  const recovery = prepareForRespawn(codeName);
10990
12139
  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)`;
12140
+ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash12("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
10992
12141
  const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
10993
12142
  const recoverySummary = recovery ? `; recovery=${recovery}` : "";
10994
12143
  log(
@@ -11002,7 +12151,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
11002
12151
  );
11003
12152
  getHostId().then((hostId) => {
11004
12153
  if (!hostId) return;
11005
- const paneTailHash = zombie.paneTail ? `sha256:${createHash11("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
12154
+ const paneTailHash = zombie.paneTail ? `sha256:${createHash12("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
11006
12155
  return api.post("/host/events", {
11007
12156
  host_id: hostId,
11008
12157
  agent_code_name: codeName,
@@ -11151,7 +12300,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
11151
12300
  if (!claudeAuthTupleBySession.has(codeName)) {
11152
12301
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
11153
12302
  }
11154
- const stableTasksHash = createHash11("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
12303
+ const stableTasksHash = createHash12("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
11155
12304
  const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
11156
12305
  if (stableTasksHash !== prevHash) {
11157
12306
  const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
@@ -11368,7 +12517,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
11368
12517
  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
12518
  void (async () => {
11370
12519
  try {
11371
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
12520
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11372
12521
  await api.post("/host/heartbeat", {
11373
12522
  host_id: hostId,
11374
12523
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11418,7 +12567,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
11418
12567
  }
11419
12568
  try {
11420
12569
  const hostId = await getHostId();
11421
- const { collectDiagnostics } = await import("../persistent-session-U5IQYEXX.js");
12570
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11422
12571
  await api.post("/host/heartbeat", {
11423
12572
  host_id: hostId,
11424
12573
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11639,6 +12788,46 @@ async function pollDirectChatMessages(agentStates) {
11639
12788
  }
11640
12789
  }
11641
12790
  }
12791
+ async function processDirectChatMessageOpencode(agent, msg) {
12792
+ if (!isOpencodeSessionHealthy(agent.codeName)) {
12793
+ log(`[direct-chat] opencode server not running for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
12794
+ return;
12795
+ }
12796
+ let result;
12797
+ try {
12798
+ result = await injectOpencodeMessage(agent.codeName, {
12799
+ channelId: "direct-chat",
12800
+ conversationKey: msg.session_id,
12801
+ senderId: "webapp",
12802
+ text: msg.content,
12803
+ // Provenance parity with the Claude Code <channel> envelope: source +
12804
+ // reply-expected, folded into the prompt by the bridge.
12805
+ meta: { source: "direct-chat", user: "webapp", lane: "conversational", requires_reply: "true" }
12806
+ });
12807
+ } catch (err) {
12808
+ log(`[direct-chat] opencode inject failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
12809
+ return;
12810
+ }
12811
+ if (result.status === "replied" && result.reply) {
12812
+ try {
12813
+ await api.post("/host/direct-chat/reply", {
12814
+ agent_id: agent.agentId,
12815
+ session_id: msg.session_id,
12816
+ content: result.reply,
12817
+ message_ids: [msg.id]
12818
+ });
12819
+ log(`[direct-chat] opencode replied for '${agent.codeName}' (msg=${msg.id}, ${result.reply.length} chars)`);
12820
+ } catch (err) {
12821
+ log(`[direct-chat] opencode reply POST failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
12822
+ }
12823
+ return;
12824
+ }
12825
+ if (result.status === "declined") {
12826
+ log(`[direct-chat] opencode gate declined message for '${agent.codeName}' (msg=${msg.id}): ${result.reason}`);
12827
+ return;
12828
+ }
12829
+ log(`[direct-chat] opencode produced no reply for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
12830
+ }
11642
12831
  async function processDirectChatMessage(agent, msg) {
11643
12832
  noteInbound(agent.codeName);
11644
12833
  const fw = agentFrameworkCache.get(agent.codeName) ?? DEFAULT_FRAMEWORK;
@@ -11655,12 +12844,16 @@ async function processDirectChatMessage(agent, msg) {
11655
12844
  }
11656
12845
  return;
11657
12846
  }
12847
+ if (fw === "opencode") {
12848
+ await processDirectChatMessageOpencode(agent, msg);
12849
+ return;
12850
+ }
11658
12851
  if (isSessionHealthy(agent.codeName)) {
11659
12852
  const useDoorbell = hostFlagStore().getBoolean("direct-chat-doorbell") || isolationMode(agent.codeName) === "docker";
11660
12853
  if (useDoorbell) {
11661
12854
  try {
11662
- const doorbell = directChatDoorbellPath(agent.agentId, homedir10());
11663
- mkdirSync7(dirname6(doorbell), { recursive: true });
12855
+ const doorbell = directChatDoorbellPath(agent.agentId, homedir11());
12856
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11664
12857
  writeFileSync8(doorbell, String(Date.now()));
11665
12858
  log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
11666
12859
  return;
@@ -11748,8 +12941,8 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
11748
12941
  }
11749
12942
  if (run_id) openInjectedRunByCode.set(codeName, run_id);
11750
12943
  try {
11751
- const doorbell = directChatDoorbellPath(agentId, homedir10());
11752
- mkdirSync7(dirname6(doorbell), { recursive: true });
12944
+ const doorbell = directChatDoorbellPath(agentId, homedir11());
12945
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11753
12946
  writeFileSync8(doorbell, String(Date.now()));
11754
12947
  } catch (err) {
11755
12948
  log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
@@ -11842,7 +13035,7 @@ async function processClaudePairSessions(agents) {
11842
13035
  killPairSession,
11843
13036
  pairTmuxSession,
11844
13037
  finalizeClaudePairOnboarding
11845
- } = await import("../claude-pair-runtime-55LJJ7DB.js");
13038
+ } = await import("../claude-pair-runtime-DO6OWWGD.js");
11846
13039
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11847
13040
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11848
13041
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -11877,12 +13070,12 @@ async function processClaudePairSessions(agents) {
11877
13070
  try {
11878
13071
  if (session.status === "initiating") {
11879
13072
  log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);
11880
- const spawn = await spawnPairSession(pairSession);
11881
- if (!spawn.ok) {
13073
+ const spawn2 = await spawnPairSession(pairSession);
13074
+ if (!spawn2.ok) {
11882
13075
  await reportAndCleanup(session.pair_id, {
11883
13076
  status: "failure",
11884
- error_code: spawn.error.kind,
11885
- error_message: spawn.error.kind === "unknown" ? spawn.error.message : void 0
13077
+ error_code: spawn2.error.kind,
13078
+ error_message: spawn2.error.kind === "unknown" ? spawn2.error.message : void 0
11886
13079
  });
11887
13080
  continue;
11888
13081
  }
@@ -12085,8 +13278,8 @@ function parseMemoryFile(raw, fallbackName) {
12085
13278
  };
12086
13279
  }
12087
13280
  async function syncMemories(agent, configDir, log2) {
12088
- const projectDir = join19(configDir, agent.code_name, "project");
12089
- const memoryDir = join19(projectDir, "memory");
13281
+ const projectDir = join20(configDir, agent.code_name, "project");
13282
+ const memoryDir = join20(projectDir, "memory");
12090
13283
  const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
12091
13284
  if (isFreshSync) {
12092
13285
  log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
@@ -12097,15 +13290,15 @@ async function syncMemories(agent, configDir, log2) {
12097
13290
  }
12098
13291
  pendingFreshMemorySync.delete(agent.agent_id);
12099
13292
  }
12100
- if (existsSync10(memoryDir)) {
13293
+ if (existsSync11(memoryDir)) {
12101
13294
  const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
12102
13295
  const currentHashes = /* @__PURE__ */ new Map();
12103
13296
  const changedMemories = [];
12104
13297
  for (const file of readdirSync5(memoryDir)) {
12105
13298
  if (!file.endsWith(".md")) continue;
12106
13299
  try {
12107
- const raw = readFileSync16(join19(memoryDir, file), "utf-8");
12108
- const fileHash = createHash11("sha256").update(raw).digest("hex").slice(0, 16);
13300
+ const raw = readFileSync17(join20(memoryDir, file), "utf-8");
13301
+ const fileHash = createHash12("sha256").update(raw).digest("hex").slice(0, 16);
12109
13302
  currentHashes.set(file, fileHash);
12110
13303
  if (prevHashes.get(file) === fileHash) continue;
12111
13304
  const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
@@ -12129,7 +13322,7 @@ async function syncMemories(agent, configDir, log2) {
12129
13322
  } catch (err) {
12130
13323
  for (const mem of changedMemories) {
12131
13324
  for (const [file] of currentHashes) {
12132
- const parsed = parseMemoryFile(readFileSync16(join19(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
13325
+ const parsed = parseMemoryFile(readFileSync17(join20(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
12133
13326
  if (parsed?.name === mem.name) currentHashes.delete(file);
12134
13327
  }
12135
13328
  }
@@ -12142,29 +13335,29 @@ async function syncMemories(agent, configDir, log2) {
12142
13335
  }
12143
13336
  }
12144
13337
  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);
13338
+ const localFiles = existsSync11(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
13339
+ const localListHash = createHash12("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
12147
13340
  const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
12148
13341
  const prevDownload = lastDownloadHash.get(agent.agent_id);
12149
13342
  try {
12150
13343
  const dbMemories = await api.post("/host/memories", {
12151
13344
  agent_id: agent.agent_id
12152
13345
  });
12153
- const responseHash = createHash11("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
13346
+ const responseHash = createHash12("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
12154
13347
  if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
12155
13348
  return true;
12156
13349
  }
12157
13350
  lastDownloadHash.set(agent.agent_id, responseHash);
12158
13351
  lastLocalFileHash.set(agent.agent_id, localListHash);
12159
13352
  if (dbMemories.memories?.length) {
12160
- mkdirSync7(memoryDir, { recursive: true });
13353
+ mkdirSync8(memoryDir, { recursive: true });
12161
13354
  let written = 0;
12162
13355
  let overwritten = 0;
12163
13356
  for (let i = 0; i < dbMemories.memories.length; i++) {
12164
13357
  const mem = dbMemories.memories[i];
12165
13358
  const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
12166
13359
  const slug = rawSlug || `memory-${i}`;
12167
- const filePath = join19(memoryDir, `${slug}.md`);
13360
+ const filePath = join20(memoryDir, `${slug}.md`);
12168
13361
  const desired = `---
12169
13362
  name: ${JSON.stringify(mem.name)}
12170
13363
  type: ${mem.type}
@@ -12173,10 +13366,10 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
12173
13366
 
12174
13367
  ${mem.content}
12175
13368
  `;
12176
- if (existsSync10(filePath)) {
13369
+ if (existsSync11(filePath)) {
12177
13370
  let existing = "";
12178
13371
  try {
12179
- existing = readFileSync16(filePath, "utf-8");
13372
+ existing = readFileSync17(filePath, "utf-8");
12180
13373
  } catch {
12181
13374
  }
12182
13375
  if (existing === desired) continue;
@@ -12189,7 +13382,7 @@ ${mem.content}
12189
13382
  }
12190
13383
  if (written > 0 || overwritten > 0) {
12191
13384
  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));
13385
+ lastLocalFileHash.set(agent.agent_id, createHash12("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
12193
13386
  log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
12194
13387
  }
12195
13388
  }
@@ -12200,7 +13393,7 @@ ${mem.content}
12200
13393
  }
12201
13394
  }
12202
13395
  async function cleanupAgentFiles(codeName, agentDir) {
12203
- if (existsSync10(agentDir)) {
13396
+ if (existsSync11(agentDir)) {
12204
13397
  try {
12205
13398
  rmSync4(agentDir, { recursive: true, force: true });
12206
13399
  log(`Removed provision directory for '${codeName}'`);
@@ -12224,8 +13417,8 @@ var caffeinateProc = null;
12224
13417
  async function startCaffeinate() {
12225
13418
  if (process.platform !== "darwin") return;
12226
13419
  try {
12227
- const { spawn } = await import("child_process");
12228
- caffeinateProc = spawn("caffeinate", ["-dims"], {
13420
+ const { spawn: spawn2 } = await import("child_process");
13421
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
12229
13422
  stdio: "ignore",
12230
13423
  detached: false
12231
13424
  });
@@ -12372,8 +13565,8 @@ async function killAllAgtTmuxSessions() {
12372
13565
  ["list-sessions", "-F", "#{session_name}"],
12373
13566
  { timeout: 2e3, stdin: "ignore" }
12374
13567
  );
12375
- const sessions = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
12376
- for (const session of sessions) {
13568
+ const sessions2 = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
13569
+ for (const session of sessions2) {
12377
13570
  try {
12378
13571
  await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
12379
13572
  timeout: 2e3,
@@ -12383,8 +13576,8 @@ async function killAllAgtTmuxSessions() {
12383
13576
  } catch {
12384
13577
  }
12385
13578
  }
12386
- if (sessions.length > 0) {
12387
- log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);
13579
+ if (sessions2.length > 0) {
13580
+ log(`Cleaned up ${sessions2.length} agt-* tmux session(s)`);
12388
13581
  }
12389
13582
  } catch {
12390
13583
  }
@@ -12420,6 +13613,7 @@ async function stopPolling(opts = {}) {
12420
13613
  const subsysStartedAt = Date.now();
12421
13614
  stopCaffeinate();
12422
13615
  stopRealtimeChat();
13616
+ stopAllOpencodeSlackIngests(log);
12423
13617
  log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
12424
13618
  for (const codeName of [...scheduledRunsByCode.keys()]) {
12425
13619
  closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
@@ -12434,8 +13628,8 @@ function startManager(opts) {
12434
13628
  config = opts;
12435
13629
  try {
12436
13630
  const stateFile = getStateFile();
12437
- if (existsSync10(stateFile)) {
12438
- const raw = readFileSync16(stateFile, "utf-8");
13631
+ if (existsSync11(stateFile)) {
13632
+ const raw = readFileSync17(stateFile, "utf-8");
12439
13633
  const parsed = JSON.parse(raw);
12440
13634
  if (Array.isArray(parsed.agents)) {
12441
13635
  state6.agents = parsed.agents;
@@ -12462,7 +13656,7 @@ function startManager(opts) {
12462
13656
  log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
12463
13657
  }
12464
13658
  log(
12465
- `[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join19(homedir10(), ".augmented", "manager.log")}`
13659
+ `[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join20(homedir11(), ".augmented", "manager.log")}`
12466
13660
  );
12467
13661
  deployMcpAssets();
12468
13662
  reapOrphanChannelMcps({ log });
@@ -12483,7 +13677,7 @@ async function reapOrphanedClaudePids() {
12483
13677
  const looksLikeClaude = (pid) => {
12484
13678
  if (process.platform !== "linux") return true;
12485
13679
  try {
12486
- const comm = readFileSync16(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
13680
+ const comm = readFileSync17(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
12487
13681
  return comm.includes("claude");
12488
13682
  } catch {
12489
13683
  return false;
@@ -12501,16 +13695,16 @@ async function reapOrphanedClaudePids() {
12501
13695
  },
12502
13696
  looksLikeClaude
12503
13697
  );
12504
- for (const spawn of decision.toKill) {
13698
+ for (const spawn2 of decision.toKill) {
12505
13699
  try {
12506
- process.kill(spawn.pid, "SIGKILL");
13700
+ process.kill(spawn2.pid, "SIGKILL");
12507
13701
  } catch (err) {
12508
- log(`[drain] reaper SIGKILL pid=${spawn.pid} failed: ${err.message}`);
13702
+ log(`[drain] reaper SIGKILL pid=${spawn2.pid} failed: ${err.message}`);
12509
13703
  }
12510
13704
  }
12511
- for (const spawn of decision.pidReusedSkipped) {
13705
+ for (const spawn2 of decision.pidReusedSkipped) {
12512
13706
  log(
12513
- `[drain] reaper skipped pid=${spawn.pid} \u2014 PID reuse suspected (identity probe rejected)`
13707
+ `[drain] reaper skipped pid=${spawn2.pid} \u2014 PID reuse suspected (identity probe rejected)`
12514
13708
  );
12515
13709
  }
12516
13710
  log(formatReaperBootLine({
@@ -12580,14 +13774,14 @@ function restartRunningChannelMcps(basenames) {
12580
13774
  }
12581
13775
  }
12582
13776
  function deployMcpAssets() {
12583
- const targetDir = join19(homedir10(), ".augmented", "_mcp");
12584
- mkdirSync7(targetDir, { recursive: true });
13777
+ const targetDir = join20(homedir11(), ".augmented", "_mcp");
13778
+ mkdirSync8(targetDir, { recursive: true });
12585
13779
  const moduleDir = dirname6(fileURLToPath(import.meta.url));
12586
13780
  let mcpSourceDir = "";
12587
13781
  let dir = moduleDir;
12588
13782
  for (let i = 0; i < 6; i++) {
12589
- const candidate = join19(dir, "dist", "mcp");
12590
- if (existsSync10(join19(candidate, "index.js"))) {
13783
+ const candidate = join20(dir, "dist", "mcp");
13784
+ if (existsSync11(join20(candidate, "index.js"))) {
12591
13785
  mcpSourceDir = candidate;
12592
13786
  break;
12593
13787
  }
@@ -12602,8 +13796,8 @@ function deployMcpAssets() {
12602
13796
  const changedBasenames = [];
12603
13797
  const fileHash = (p) => {
12604
13798
  try {
12605
- if (!existsSync10(p)) return null;
12606
- return createHash11("sha256").update(readFileSync16(p)).digest("hex");
13799
+ if (!existsSync11(p)) return null;
13800
+ return createHash12("sha256").update(readFileSync17(p)).digest("hex");
12607
13801
  } catch {
12608
13802
  return null;
12609
13803
  }
@@ -12667,9 +13861,9 @@ function deployMcpAssets() {
12667
13861
  // needs restarting to pick up a token rotation.
12668
13862
  "xero.js"
12669
13863
  ]) {
12670
- const src = join19(mcpSourceDir, file);
12671
- const dst = join19(targetDir, file);
12672
- if (!existsSync10(src)) continue;
13864
+ const src = join20(mcpSourceDir, file);
13865
+ const dst = join20(targetDir, file);
13866
+ if (!existsSync11(src)) continue;
12673
13867
  const before = fileHash(dst);
12674
13868
  try {
12675
13869
  copyFileSync(src, dst);
@@ -12686,16 +13880,16 @@ function deployMcpAssets() {
12686
13880
  log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
12687
13881
  restartRunningChannelMcps(changedBasenames);
12688
13882
  }
12689
- const localMcpPath = join19(targetDir, "index.js");
13883
+ const localMcpPath = join20(targetDir, "index.js");
12690
13884
  try {
12691
- const agentsDir = join19(homedir10(), ".augmented", "agents");
12692
- if (existsSync10(agentsDir)) {
13885
+ const agentsDir = join20(homedir11(), ".augmented", "agents");
13886
+ if (existsSync11(agentsDir)) {
12693
13887
  for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
12694
13888
  if (!entry.isDirectory()) continue;
12695
13889
  for (const subdir of ["provision", "project"]) {
12696
- const mcpJsonPath = join19(agentsDir, entry.name, subdir, ".mcp.json");
13890
+ const mcpJsonPath = join20(agentsDir, entry.name, subdir, ".mcp.json");
12697
13891
  try {
12698
- const raw = readFileSync16(mcpJsonPath, "utf-8");
13892
+ const raw = readFileSync17(mcpJsonPath, "utf-8");
12699
13893
  if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
12700
13894
  const mcpConfig = JSON.parse(raw);
12701
13895
  const augServer = mcpConfig.mcpServers?.["augmented"];