@integrity-labs/agt-cli 0.28.318 → 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,
@@ -26,6 +28,7 @@ import {
26
28
  getCachedClaudeAuthMode,
27
29
  getHostId,
28
30
  givenUpMcpServerKeys,
31
+ liveProxyExtraHeaderVars,
29
32
  provision,
30
33
  provisionAutoKanbanProgressHook,
31
34
  provisionChannelProgressHook,
@@ -34,12 +37,13 @@ import {
34
37
  provisionPreCompactHook,
35
38
  provisionSessionStateHook,
36
39
  provisionStopHook,
40
+ readChannelServerEnv,
37
41
  reapMissingMcpSessions,
38
42
  reapStaleMcpChildren,
39
43
  requireHost,
40
44
  safeWriteJsonAtomic,
41
45
  setConfigHash
42
- } from "../chunk-2DIWTRTU.js";
46
+ } from "../chunk-CJSATK6B.js";
43
47
  import {
44
48
  getProjectDir as getProjectDir2,
45
49
  getReadyTasks,
@@ -48,6 +52,7 @@ import {
48
52
  syncTasksToScheduler
49
53
  } from "../chunk-I3YS5WFV.js";
50
54
  import {
55
+ AnchorSessionClient,
51
56
  CONVERSATION_FAILURE_CATEGORIES,
52
57
  DEFAULT_FRAMEWORK,
53
58
  FLAGS_SCHEMA_VERSION,
@@ -127,17 +132,17 @@ import {
127
132
  takeZombieDetection,
128
133
  transcriptActivityAgeSeconds,
129
134
  writeEgressAllowlist
130
- } from "../chunk-4DHYHNCV.js";
135
+ } from "../chunk-NAS4DZNG.js";
131
136
  import {
132
137
  reapOrphanChannelMcps
133
138
  } from "../chunk-XWVM4KPK.js";
134
139
 
135
140
  // src/lib/manager-worker.ts
136
- import { createHash as createHash11 } from "crypto";
137
- 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";
138
143
  import { execFileSync as syncExecFile } from "child_process";
139
- import { join as join19, dirname as dirname6 } from "path";
140
- import { homedir as homedir10 } from "os";
144
+ import { join as join20, dirname as dirname6 } from "path";
145
+ import { homedir as homedir11 } from "os";
141
146
  import { fileURLToPath } from "url";
142
147
 
143
148
  // src/lib/claude-code-upgrade-throttle.ts
@@ -279,6 +284,95 @@ function withResolvedRemoteMcp(integration) {
279
284
  };
280
285
  }
281
286
 
287
+ // src/lib/anchor-session-lifecycle.ts
288
+ var DEFAULT_ANCHOR_SESSION_MAX_DURATION_MINUTES = 60;
289
+ var DEFAULT_ANCHOR_SESSION_REFRESH_MARGIN_MINUTES = 5;
290
+ var AnchorSessionLifecycle = class {
291
+ store = /* @__PURE__ */ new Map();
292
+ maxDurationMs;
293
+ refreshMarginMs;
294
+ idleTimeoutMinutes;
295
+ createClient;
296
+ constructor(opts = {}) {
297
+ const maxDurationMinutes = opts.maxDurationMinutes ?? DEFAULT_ANCHOR_SESSION_MAX_DURATION_MINUTES;
298
+ this.maxDurationMs = maxDurationMinutes * 6e4;
299
+ this.refreshMarginMs = (opts.refreshMarginMinutes ?? DEFAULT_ANCHOR_SESSION_REFRESH_MARGIN_MINUTES) * 6e4;
300
+ this.idleTimeoutMinutes = opts.idleTimeoutMinutes ?? maxDurationMinutes;
301
+ this.createClient = opts.createClient ?? ((apiKey) => new AnchorSessionClient({ apiKey }));
302
+ }
303
+ /**
304
+ * Return the session id the agent should bind, minting or re-minting as needed.
305
+ * Returns null for the stateless case (no profile / no key / mint failure with
306
+ * no valid fallback) - the manager then leaves `ANCHOR_BROWSER_SESSION_ID`
307
+ * empty. Never throws.
308
+ */
309
+ async ensure(params) {
310
+ const { agentId, now } = params;
311
+ const log2 = params.log ?? (() => {
312
+ });
313
+ const profileName = params.profileName?.trim() || "";
314
+ const apiKey = params.apiKey?.trim() || "";
315
+ const existing = this.store.get(agentId);
316
+ if (!profileName) {
317
+ if (existing) {
318
+ this.endBestEffort(apiKey, existing.sessionId, log2);
319
+ this.store.delete(agentId);
320
+ }
321
+ return null;
322
+ }
323
+ if (!apiKey) {
324
+ if (existing && existing.profileName === profileName && now < existing.expiresAt) {
325
+ return existing.sessionId;
326
+ }
327
+ log2(`[anchor-session] ${agentId}: profile "${profileName}" but no api key - stateless`);
328
+ return null;
329
+ }
330
+ if (existing && existing.profileName === profileName && now < existing.expiresAt - this.refreshMarginMs) {
331
+ return existing.sessionId;
332
+ }
333
+ try {
334
+ const client2 = this.createClient(apiKey);
335
+ const session = await client2.createSession({
336
+ profileName,
337
+ dedicatedStickyIp: true,
338
+ maxDurationMinutes: this.maxDurationMs / 6e4,
339
+ idleTimeoutMinutes: this.idleTimeoutMinutes
340
+ });
341
+ const effectiveMs = Math.min(this.maxDurationMs, this.idleTimeoutMinutes * 6e4);
342
+ this.store.set(agentId, {
343
+ sessionId: session.sessionId,
344
+ profileName,
345
+ expiresAt: now + effectiveMs
346
+ });
347
+ if (existing && existing.sessionId !== session.sessionId) {
348
+ this.endBestEffort(apiKey, existing.sessionId, log2);
349
+ }
350
+ log2(`[anchor-session] ${agentId}: minted session for profile "${profileName}"`);
351
+ return session.sessionId;
352
+ } catch (err) {
353
+ const reason = err instanceof Error ? err.message : String(err);
354
+ if (existing && existing.profileName === profileName && now < existing.expiresAt) {
355
+ log2(`[anchor-session] ${agentId}: re-mint failed (${reason}) - keeping current session`);
356
+ return existing.sessionId;
357
+ }
358
+ log2(`[anchor-session] ${agentId}: mint failed (${reason}) - degrading to stateless`);
359
+ return null;
360
+ }
361
+ }
362
+ /** Forget an agent's session (e.g. on decommission). Best-effort ends it. */
363
+ forget(agentId, apiKey, log2 = () => {
364
+ }) {
365
+ const existing = this.store.get(agentId);
366
+ if (!existing) return;
367
+ this.endBestEffort(apiKey?.trim() || "", existing.sessionId, log2);
368
+ this.store.delete(agentId);
369
+ }
370
+ endBestEffort(apiKey, sessionId, log2) {
371
+ if (!apiKey || !sessionId) return;
372
+ void this.createClient(apiKey).endSession(sessionId).catch((err) => log2(`[anchor-session] failed to end a stale session: ${err.message}`));
373
+ }
374
+ };
375
+
282
376
  // src/lib/channel-restart-decision.ts
283
377
  function launchableChannelIds(channelConfigs) {
284
378
  if (!channelConfigs) return /* @__PURE__ */ new Set();
@@ -5347,6 +5441,1103 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
5347
5441
  }
5348
5442
  }
5349
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
+
5350
6541
  // src/lib/wedge-detection.ts
5351
6542
  var DEFAULTS = {
5352
6543
  inboundWaitSeconds: 120,
@@ -5487,24 +6678,24 @@ function partitionActionableByPoison(actionable, states, config2) {
5487
6678
  }
5488
6679
 
5489
6680
  // src/lib/restart-flags.ts
5490
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync15, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
5491
- import { homedir as homedir9 } from "os";
5492
- 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";
5493
6684
  import { randomUUID } from "crypto";
5494
6685
  function restartFlagsDir() {
5495
- return join18(homedir9(), ".augmented", "restart-flags");
6686
+ return join19(homedir10(), ".augmented", "restart-flags");
5496
6687
  }
5497
6688
  function flagPath(codeName) {
5498
- return join18(restartFlagsDir(), `${codeName}.flag`);
6689
+ return join19(restartFlagsDir(), `${codeName}.flag`);
5499
6690
  }
5500
6691
  function readRestartFlags() {
5501
6692
  const dir = restartFlagsDir();
5502
- if (!existsSync9(dir)) return [];
6693
+ if (!existsSync10(dir)) return [];
5503
6694
  const out = [];
5504
6695
  for (const entry of readdirSync4(dir)) {
5505
6696
  if (!entry.endsWith(".flag")) continue;
5506
6697
  try {
5507
- const raw = readFileSync15(join18(dir, entry), "utf8");
6698
+ const raw = readFileSync16(join19(dir, entry), "utf8");
5508
6699
  const parsed = JSON.parse(raw);
5509
6700
  if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
5510
6701
  parsed.codeName = entry.replace(/\.flag$/, "");
@@ -5522,7 +6713,7 @@ function readRestartFlags() {
5522
6713
  }
5523
6714
  function deleteRestartFlag(codeName) {
5524
6715
  const path = flagPath(codeName);
5525
- if (existsSync9(path)) {
6716
+ if (existsSync10(path)) {
5526
6717
  rmSync3(path, { force: true });
5527
6718
  }
5528
6719
  }
@@ -5868,10 +7059,10 @@ function startRealtimeConfig(config2) {
5868
7059
  filter: filterStr
5869
7060
  }, (payload) => {
5870
7061
  const agent = payload.new;
5871
- 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}`;
5872
7063
  const prev = lastKnown.get(agent.agent_id);
5873
- if (prev === fingerprint) return;
5874
- lastKnown.set(agent.agent_id, fingerprint);
7064
+ if (prev === fingerprint2) return;
7065
+ lastKnown.set(agent.agent_id, fingerprint2);
5875
7066
  log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
5876
7067
  onConfigChange(agent);
5877
7068
  }).subscribe((status) => {
@@ -6739,20 +7930,21 @@ function isHostBusyForForcedUpdate() {
6739
7930
  return false;
6740
7931
  }
6741
7932
  var runningMcpHashes = /* @__PURE__ */ new Map();
7933
+ var anchorSessionLifecycle = new AnchorSessionLifecycle();
6742
7934
  var runningMcpServerKeys = /* @__PURE__ */ new Map();
6743
7935
  var runningChannelSecretHashes = /* @__PURE__ */ new Map();
6744
7936
  var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
6745
7937
  function projectMcpHash(_codeName, projectDir) {
6746
7938
  try {
6747
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
6748
- 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");
6749
7941
  } catch {
6750
7942
  return null;
6751
7943
  }
6752
7944
  }
6753
7945
  function projectMcpKeys(_codeName, projectDir) {
6754
7946
  try {
6755
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7947
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6756
7948
  const parsed = JSON.parse(raw);
6757
7949
  const servers = parsed.mcpServers;
6758
7950
  if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
@@ -6770,7 +7962,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
6770
7962
  else runningMcpServerKeys.delete(codeName);
6771
7963
  let launchStructure = null;
6772
7964
  try {
6773
- const raw = readFileSync16(join19(projectDir, ".mcp.json"), "utf-8");
7965
+ const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
6774
7966
  launchStructure = managedMcpStructureHashFromFile(
6775
7967
  JSON.parse(raw),
6776
7968
  isManagedMcpServerKey
@@ -6870,7 +8062,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
6870
8062
  if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
6871
8063
  let mcpJsonForRebind = null;
6872
8064
  try {
6873
- mcpJsonForRebind = JSON.parse(readFileSync16(join19(projectDir, ".mcp.json"), "utf-8"));
8065
+ mcpJsonForRebind = JSON.parse(readFileSync17(join20(projectDir, ".mcp.json"), "utf-8"));
6874
8066
  } catch {
6875
8067
  mcpJsonForRebind = null;
6876
8068
  }
@@ -6999,7 +8191,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
6999
8191
  function projectChannelSecretHash(projectDir) {
7000
8192
  try {
7001
8193
  const entries = parseEnvIntegrations(
7002
- readFileSync16(join19(projectDir, ".env.integrations"), "utf-8")
8194
+ readFileSync17(join20(projectDir, ".env.integrations"), "utf-8")
7003
8195
  );
7004
8196
  return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
7005
8197
  } catch {
@@ -7061,6 +8253,7 @@ function resolveAgentFramework(codeName) {
7061
8253
  function clearAgentCaches(agentId, codeName) {
7062
8254
  const channelCacheMutated = clearAgentState(agentId, codeName);
7063
8255
  agentChannelTokens.delete(codeName);
8256
+ stopOpencodeSlackIngest(codeName, log);
7064
8257
  agentFrameworkCache.delete(codeName);
7065
8258
  kanbanBoardCache.delete(codeName);
7066
8259
  notifyBoardCache.delete(codeName);
@@ -7087,7 +8280,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
7087
8280
  var lastVersionCheckAt = 0;
7088
8281
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7089
8282
  var lastResponsivenessProbeAt = 0;
7090
- var agtCliVersion = true ? "0.28.318" : "dev";
8283
+ var agtCliVersion = true ? "0.28.320" : "dev";
7091
8284
  function resolveBrewPath(execFileSync2) {
7092
8285
  try {
7093
8286
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -7100,7 +8293,7 @@ function resolveBrewPath(execFileSync2) {
7100
8293
  "/usr/local/bin/brew"
7101
8294
  ];
7102
8295
  for (const path of fallbacks) {
7103
- if (existsSync10(path)) return path;
8296
+ if (existsSync11(path)) return path;
7104
8297
  }
7105
8298
  return null;
7106
8299
  }
@@ -7110,7 +8303,7 @@ function claudeBinaryInstalled(execFileSync2) {
7110
8303
  "/opt/homebrew/bin/claude",
7111
8304
  "/usr/local/bin/claude"
7112
8305
  ];
7113
- if (canonical.some((path) => existsSync10(path))) return true;
8306
+ if (canonical.some((path) => existsSync11(path))) return true;
7114
8307
  try {
7115
8308
  execFileSync2("which", ["claude"], { timeout: 5e3 });
7116
8309
  return true;
@@ -7146,7 +8339,7 @@ async function ensureToolkitCli(toolkitSlug) {
7146
8339
  }
7147
8340
  const { binary, installer, package: pkg, script } = integration.cli_tool;
7148
8341
  const resolvedInstaller = installer ?? "manual";
7149
- const { execFileSync: execFileSync2, execSync } = await import("child_process");
8342
+ const { execFileSync: execFileSync2, execSync: execSync2 } = await import("child_process");
7150
8343
  try {
7151
8344
  execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
7152
8345
  toolkitCliEnsured.add(toolkitSlug);
@@ -7197,7 +8390,7 @@ async function ensureToolkitCli(toolkitSlug) {
7197
8390
  return;
7198
8391
  }
7199
8392
  log(`[toolkit-install] ${toolkitSlug}: running declared install script\u2026`);
7200
- execSync(script, { timeout: 18e4, stdio: "pipe" });
8393
+ execSync2(script, { timeout: 18e4, stdio: "pipe" });
7201
8394
  }
7202
8395
  } catch (err) {
7203
8396
  const e = err;
@@ -7221,8 +8414,8 @@ async function ensureToolkitCli(toolkitSlug) {
7221
8414
  }
7222
8415
  function runAsync(cmd, args, opts) {
7223
8416
  return new Promise((resolve, reject) => {
7224
- import("child_process").then(({ spawn }) => {
7225
- 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 });
7226
8419
  let stdout = "";
7227
8420
  let stderr = "";
7228
8421
  let settled = false;
@@ -7265,8 +8458,8 @@ function claudeManagedSettingsPath() {
7265
8458
  function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7266
8459
  try {
7267
8460
  let settings = {};
7268
- if (existsSync10(path)) {
7269
- const raw = readFileSync16(path, "utf-8").trim();
8461
+ if (existsSync11(path)) {
8462
+ const raw = readFileSync17(path, "utf-8").trim();
7270
8463
  if (raw) {
7271
8464
  let parsed;
7272
8465
  try {
@@ -7282,7 +8475,7 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
7282
8475
  }
7283
8476
  if (settings.channelsEnabled === true) return "ok";
7284
8477
  settings.channelsEnabled = true;
7285
- mkdirSync7(dirname6(path), { recursive: true });
8478
+ mkdirSync8(dirname6(path), { recursive: true });
7286
8479
  writeFileSync8(path, `${JSON.stringify(settings, null, 2)}
7287
8480
  `);
7288
8481
  log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
@@ -7328,7 +8521,7 @@ async function ensureFrameworkBinary(frameworkId) {
7328
8521
  if (!process.env.PATH?.split(":").includes(brewBinDir)) {
7329
8522
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
7330
8523
  }
7331
- if (existsSync10("/home/linuxbrew/.linuxbrew/bin/claude")) {
8524
+ if (existsSync11("/home/linuxbrew/.linuxbrew/bin/claude")) {
7332
8525
  log("Claude Code installed successfully");
7333
8526
  } else {
7334
8527
  log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
@@ -7384,7 +8577,7 @@ ${r.stderr}`;
7384
8577
  }
7385
8578
  var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
7386
8579
  function selfUpdateAppliedMarkerPath() {
7387
- return join19(homedir10(), ".augmented", ".last-self-update-applied");
8580
+ return join20(homedir11(), ".augmented", ".last-self-update-applied");
7388
8581
  }
7389
8582
  var selfUpdateUpToDateLogged = false;
7390
8583
  var restartAfterUpgrade = false;
@@ -7408,7 +8601,7 @@ async function checkAndUpdateCli(opts) {
7408
8601
  const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
7409
8602
  if (!isBrewFormula && !isNpmGlobal) return "noop";
7410
8603
  const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
7411
- const markerPath = join19(homedir10(), ".augmented", ".last-update-check");
8604
+ const markerPath = join20(homedir11(), ".augmented", ".last-update-check");
7412
8605
  if (!force) {
7413
8606
  try {
7414
8607
  const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
@@ -7686,13 +8879,13 @@ async function checkClaudeAuth() {
7686
8879
  }
7687
8880
  var evalEmptyMcpConfigPath = null;
7688
8881
  function ensureEvalEmptyMcpConfig() {
7689
- if (evalEmptyMcpConfigPath && existsSync10(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
7690
- const dir = join19(homedir10(), ".augmented");
8882
+ if (evalEmptyMcpConfigPath && existsSync11(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
8883
+ const dir = join20(homedir11(), ".augmented");
7691
8884
  try {
7692
- mkdirSync7(dir, { recursive: true });
8885
+ mkdirSync8(dir, { recursive: true });
7693
8886
  } catch {
7694
8887
  }
7695
- const p = join19(dir, ".eval-empty-mcp.json");
8888
+ const p = join20(dir, ".eval-empty-mcp.json");
7696
8889
  writeFileSync8(p, JSON.stringify({ mcpServers: {} }));
7697
8890
  evalEmptyMcpConfigPath = p;
7698
8891
  return p;
@@ -7718,7 +8911,7 @@ async function runEvalClaude(prompt, model) {
7718
8911
  ""
7719
8912
  ];
7720
8913
  const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), args, {
7721
- cwd: homedir10(),
8914
+ cwd: homedir11(),
7722
8915
  timeout: 12e4,
7723
8916
  stdin: "ignore",
7724
8917
  env: childEnv,
@@ -7784,10 +8977,10 @@ function resolveConversationEvalBackend() {
7784
8977
  return conversationEvalBackend;
7785
8978
  }
7786
8979
  function getStateFile() {
7787
- 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");
7788
8981
  }
7789
8982
  function channelHashCacheDir() {
7790
- return config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
8983
+ return config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7791
8984
  }
7792
8985
  function loadChannelHashCache2() {
7793
8986
  loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
@@ -7815,7 +9008,7 @@ function removeSenderPolicyBaselineEntry(agentId) {
7815
9008
  var _channelQuarantineStore = null;
7816
9009
  function channelQuarantineStore() {
7817
9010
  if (!_channelQuarantineStore) {
7818
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9011
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7819
9012
  _channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
7820
9013
  }
7821
9014
  return _channelQuarantineStore;
@@ -7823,7 +9016,7 @@ function channelQuarantineStore() {
7823
9016
  var _hostFlagStore = null;
7824
9017
  function hostFlagStore() {
7825
9018
  if (!_hostFlagStore) {
7826
- const dir = config?.configDir ?? join19(process.env["HOME"] ?? "/tmp", ".augmented");
9019
+ const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
7827
9020
  _hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
7828
9021
  }
7829
9022
  return _hostFlagStore;
@@ -7891,12 +9084,12 @@ function parseSkillFrontmatter(content) {
7891
9084
  }
7892
9085
  async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
7893
9086
  const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync9 } = await import("fs");
7894
- const skillsDir = join19(configDir, codeName, "project", ".claude", "skills");
7895
- 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");
7896
9089
  if (!ex(skillsDir) || !ex(claudeMdPath)) return;
7897
9090
  const entries = [];
7898
9091
  for (const dir of readdirSync6(skillsDir).sort()) {
7899
- const skillFile = join19(skillsDir, dir, "SKILL.md");
9092
+ const skillFile = join20(skillsDir, dir, "SKILL.md");
7900
9093
  if (!ex(skillFile)) continue;
7901
9094
  try {
7902
9095
  const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
@@ -7959,7 +9152,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
7959
9152
  if (codeNames.length === 0) return;
7960
9153
  void (async () => {
7961
9154
  try {
7962
- const { collectDiagnostics } = await import("../persistent-session-VSSEZTY7.js");
9155
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
7963
9156
  await api.post("/host/heartbeat", {
7964
9157
  host_id: hostId,
7965
9158
  agent_diagnostics: collectDiagnostics(codeNames)
@@ -8057,7 +9250,7 @@ async function pollCycle() {
8057
9250
  }
8058
9251
  try {
8059
9252
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
8060
- const { collectDiagnostics } = await import("../persistent-session-VSSEZTY7.js");
9253
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
8061
9254
  const diagCodeNames = [...agentState.persistentSessionAgents];
8062
9255
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
8063
9256
  let tailscaleHostname;
@@ -8078,8 +9271,8 @@ async function pollCycle() {
8078
9271
  }
8079
9272
  let osUsername;
8080
9273
  try {
8081
- const { userInfo } = await import("os");
8082
- osUsername = userInfo().username;
9274
+ const { userInfo: userInfo2 } = await import("os");
9275
+ osUsername = userInfo2().username;
8083
9276
  } catch {
8084
9277
  }
8085
9278
  let claudeAuth = null;
@@ -8087,7 +9280,7 @@ async function pollCycle() {
8087
9280
  claudeAuth = await detectClaudeAuth();
8088
9281
  } catch (err) {
8089
9282
  const errText = err instanceof Error ? err.message : String(err);
8090
- const errId = createHash11("sha256").update(errText).digest("hex").slice(0, 12);
9283
+ const errId = createHash12("sha256").update(errText).digest("hex").slice(0, 12);
8091
9284
  log(`Claude auth detection failed (error_id=${errId})`);
8092
9285
  }
8093
9286
  const hostHasClaudeCode = state6.agents.some(
@@ -8206,7 +9399,7 @@ async function pollCycle() {
8206
9399
  const {
8207
9400
  collectResponsivenessProbes,
8208
9401
  getResponsivenessIntervalMs
8209
- } = await import("../responsiveness-probe-I6YFUKYD.js");
9402
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8210
9403
  const probeIntervalMs = getResponsivenessIntervalMs();
8211
9404
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
8212
9405
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -8238,7 +9431,7 @@ async function pollCycle() {
8238
9431
  collectResponsivenessProbes,
8239
9432
  livePendingInboundOldestAgeSeconds,
8240
9433
  parkPendingInbound
8241
- } = await import("../responsiveness-probe-I6YFUKYD.js");
9434
+ } = await import("../responsiveness-probe-NARLJJGH.js");
8242
9435
  const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
8243
9436
  const wedgeNow = /* @__PURE__ */ new Date();
8244
9437
  const liveAgents = agentState.persistentSessionAgents;
@@ -8327,13 +9520,13 @@ async function pollCycle() {
8327
9520
  );
8328
9521
  if (hostFlagStore().getBoolean("wedge-transient-notice")) {
8329
9522
  try {
8330
- const paneTail = readFileSync16(paneLogPath(codeName), "utf8").slice(-65536);
9523
+ const paneTail = readFileSync17(paneLogPath(codeName), "utf8").slice(-65536);
8331
9524
  const transient = detectTransientApiErrorInLog(paneTail);
8332
9525
  if (transient) {
8333
- const wedgeHome = join19(homedir10(), ".augmented", codeName);
8334
- if (existsSync10(wedgeHome)) {
9526
+ const wedgeHome = join20(homedir11(), ".augmented", codeName);
9527
+ if (existsSync11(wedgeHome)) {
8335
9528
  atomicWriteFileSync(
8336
- join19(wedgeHome, "watchdog-give-up.json"),
9529
+ join20(wedgeHome, "watchdog-give-up.json"),
8337
9530
  JSON.stringify({
8338
9531
  gave_up_at: wedgeNow.toISOString(),
8339
9532
  reason: "transient_overload"
@@ -8582,7 +9775,7 @@ async function pollCycle() {
8582
9775
  } catch {
8583
9776
  }
8584
9777
  killAgentChannelProcesses(prev.codeName, { log });
8585
- const agentDir = join19(adapter.getAgentDir(prev.codeName), "provision");
9778
+ const agentDir = join20(adapter.getAgentDir(prev.codeName), "provision");
8586
9779
  await cleanupAgentFiles(prev.codeName, agentDir);
8587
9780
  clearAgentCaches(prev.agentId, prev.codeName);
8588
9781
  }
@@ -8669,10 +9862,10 @@ async function pollCycle() {
8669
9862
  // pending-inbound marker. Best-effort: a write failure is logged by
8670
9863
  // the watchdog, never fails the poll cycle.
8671
9864
  signalGiveUp: (codeName) => {
8672
- const dir = join19(homedir10(), ".augmented", codeName);
8673
- if (!existsSync10(dir)) return;
9865
+ const dir = join20(homedir11(), ".augmented", codeName);
9866
+ if (!existsSync11(dir)) return;
8674
9867
  atomicWriteFileSync(
8675
- join19(dir, "watchdog-give-up.json"),
9868
+ join20(dir, "watchdog-give-up.json"),
8676
9869
  JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
8677
9870
  );
8678
9871
  }
@@ -8802,7 +9995,7 @@ async function processAgent(agent, agentStates) {
8802
9995
  }
8803
9996
  const now = (/* @__PURE__ */ new Date()).toISOString();
8804
9997
  const adapter = resolveAgentFramework(agent.code_name);
8805
- let agentDir = join19(adapter.getAgentDir(agent.code_name), "provision");
9998
+ let agentDir = join20(adapter.getAgentDir(agent.code_name), "provision");
8806
9999
  if (agent.status === "draft" || agent.status === "paused") {
8807
10000
  if (previousKnownStatus !== agent.status) {
8808
10001
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
@@ -8848,7 +10041,7 @@ async function processAgent(agent, agentStates) {
8848
10041
  const residuals = {
8849
10042
  gatewayRunning: false,
8850
10043
  portAllocated: false,
8851
- provisionDirExists: existsSync10(agentDir)
10044
+ provisionDirExists: existsSync11(agentDir)
8852
10045
  };
8853
10046
  if (!hasRevokedResiduals(residuals)) {
8854
10047
  agentStates.push({
@@ -8986,7 +10179,7 @@ async function processAgent(agent, agentStates) {
8986
10179
  const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
8987
10180
  agentFrameworkCache.set(agent.code_name, frameworkId);
8988
10181
  const frameworkAdapter = getFramework(frameworkId);
8989
- agentDir = join19(frameworkAdapter.getAgentDir(agent.code_name), "provision");
10182
+ agentDir = join20(frameworkAdapter.getAgentDir(agent.code_name), "provision");
8990
10183
  cacheAgentDeliveryMetadata(agent.code_name, refreshData);
8991
10184
  agentRestartTimezoneInputs.set(agent.code_name, {
8992
10185
  agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
@@ -9031,9 +10224,9 @@ async function processAgent(agent, agentStates) {
9031
10224
  try {
9032
10225
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
9033
10226
  const changedFiles = [];
9034
- mkdirSync7(agentDir, { recursive: true });
10227
+ mkdirSync8(agentDir, { recursive: true });
9035
10228
  for (const artifact of artifacts) {
9036
- const filePath = join19(agentDir, artifact.relativePath);
10229
+ const filePath = join20(agentDir, artifact.relativePath);
9037
10230
  let existingHash;
9038
10231
  let newHash;
9039
10232
  let writeContent = artifact.content;
@@ -9052,8 +10245,8 @@ async function processAgent(agent, agentStates) {
9052
10245
  };
9053
10246
  newHash = sha256(stripDynamicSections(artifact.content));
9054
10247
  try {
9055
- const projectClaudeMd = join19(config.configDir, agent.code_name, "project", "CLAUDE.md");
9056
- 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");
9057
10250
  existingHash = sha256(stripDynamicSections(existing));
9058
10251
  } catch {
9059
10252
  existingHash = null;
@@ -9071,7 +10264,7 @@ async function processAgent(agent, agentStates) {
9071
10264
  const generatorKeys = Object.keys(generatorServers);
9072
10265
  let existingRaw = "";
9073
10266
  try {
9074
- existingRaw = readFileSync16(filePath, "utf-8");
10267
+ existingRaw = readFileSync17(filePath, "utf-8");
9075
10268
  } catch {
9076
10269
  }
9077
10270
  const existingServers = parseMcp(existingRaw);
@@ -9093,13 +10286,13 @@ async function processAgent(agent, agentStates) {
9093
10286
  }
9094
10287
  }
9095
10288
  if (changedFiles.length > 0) {
9096
- const isFirst = !existsSync10(join19(agentDir, "CHARTER.md"));
10289
+ const isFirst = !existsSync11(join20(agentDir, "CHARTER.md"));
9097
10290
  const verb = isFirst ? "Provisioning" : "Updating";
9098
10291
  const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
9099
10292
  log(`${verb} '${agent.code_name}': ${fileNames}`);
9100
10293
  for (const file of changedFiles) {
9101
- const filePath = join19(agentDir, file.relativePath);
9102
- mkdirSync7(dirname6(filePath), { recursive: true });
10294
+ const filePath = join20(agentDir, file.relativePath);
10295
+ mkdirSync8(dirname6(filePath), { recursive: true });
9103
10296
  if (file.relativePath === ".mcp.json") {
9104
10297
  safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
9105
10298
  } else {
@@ -9107,12 +10300,12 @@ async function processAgent(agent, agentStates) {
9107
10300
  }
9108
10301
  }
9109
10302
  try {
9110
- const provSkillsDir = join19(agentDir, ".claude", "skills");
9111
- if (existsSync10(provSkillsDir)) {
10303
+ const provSkillsDir = join20(agentDir, ".claude", "skills");
10304
+ if (existsSync11(provSkillsDir)) {
9112
10305
  for (const folder of readdirSync5(provSkillsDir)) {
9113
10306
  if (folder.startsWith("knowledge-")) {
9114
10307
  try {
9115
- rmSync4(join19(provSkillsDir, folder), { recursive: true });
10308
+ rmSync4(join20(provSkillsDir, folder), { recursive: true });
9116
10309
  } catch {
9117
10310
  }
9118
10311
  }
@@ -9125,7 +10318,7 @@ async function processAgent(agent, agentStates) {
9125
10318
  const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
9126
10319
  const hashes = /* @__PURE__ */ new Map();
9127
10320
  for (const file of trackedFiles2) {
9128
- const h = hashFile(join19(agentDir, file));
10321
+ const h = hashFile(join20(agentDir, file));
9129
10322
  if (h) hashes.set(file, h);
9130
10323
  }
9131
10324
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -9143,14 +10336,14 @@ async function processAgent(agent, agentStates) {
9143
10336
  }
9144
10337
  if (Array.isArray(refreshData.workflows)) {
9145
10338
  try {
9146
- const provWorkflowsDir = join19(agentDir, ".claude", "workflows");
9147
- if (existsSync10(provWorkflowsDir)) {
10339
+ const provWorkflowsDir = join20(agentDir, ".claude", "workflows");
10340
+ if (existsSync11(provWorkflowsDir)) {
9148
10341
  const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
9149
10342
  for (const file of readdirSync5(provWorkflowsDir)) {
9150
10343
  if (!file.endsWith(".js")) continue;
9151
10344
  if (expected.has(file)) continue;
9152
10345
  try {
9153
- rmSync4(join19(provWorkflowsDir, file));
10346
+ rmSync4(join20(provWorkflowsDir, file));
9154
10347
  } catch {
9155
10348
  }
9156
10349
  }
@@ -9207,10 +10400,10 @@ async function processAgent(agent, agentStates) {
9207
10400
  }
9208
10401
  let lastDriftCheckAt = now;
9209
10402
  const written = agentState.writtenHashes.get(agent.agent_id);
9210
- if (written && existsSync10(agentDir)) {
10403
+ if (written && existsSync11(agentDir)) {
9211
10404
  const driftedFiles = [];
9212
10405
  for (const [file, expectedHash] of written) {
9213
- const localHash = hashFile(join19(agentDir, file));
10406
+ const localHash = hashFile(join20(agentDir, file));
9214
10407
  if (localHash && localHash !== expectedHash) {
9215
10408
  driftedFiles.push(file);
9216
10409
  }
@@ -9221,7 +10414,7 @@ async function processAgent(agent, agentStates) {
9221
10414
  try {
9222
10415
  const localHashes = {};
9223
10416
  for (const file of driftedFiles) {
9224
- localHashes[file] = hashFile(join19(agentDir, file));
10417
+ localHashes[file] = hashFile(join20(agentDir, file));
9225
10418
  }
9226
10419
  await api.post("/host/drift", {
9227
10420
  agent_id: agent.agent_id,
@@ -9408,15 +10601,15 @@ async function processAgent(agent, agentStates) {
9408
10601
  const addedChannels = [...restartDecision.added];
9409
10602
  const writeDmNoticeMarkers = isChannelAddRestart ? () => {
9410
10603
  try {
9411
- const agentAugmentedDir = join19(homedir10(), ".augmented", agent.code_name);
9412
- mkdirSync7(agentAugmentedDir, { recursive: true });
10604
+ const agentAugmentedDir = join20(homedir11(), ".augmented", agent.code_name);
10605
+ mkdirSync8(agentAugmentedDir, { recursive: true });
9413
10606
  const markerJson = JSON.stringify({
9414
10607
  version: 1,
9415
10608
  at: (/* @__PURE__ */ new Date()).toISOString(),
9416
10609
  added: addedChannels
9417
10610
  });
9418
10611
  for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
9419
- atomicWriteFileSync(join19(agentAugmentedDir, file), markerJson);
10612
+ atomicWriteFileSync(join20(agentAugmentedDir, file), markerJson);
9420
10613
  }
9421
10614
  } catch (err) {
9422
10615
  log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
@@ -9492,7 +10685,7 @@ async function processAgent(agent, agentStates) {
9492
10685
  const behaviourSubset = extractMsTeamsBehaviourSubset(
9493
10686
  msteamsEntry?.config
9494
10687
  );
9495
- const behaviourHash = createHash11("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
10688
+ const behaviourHash = createHash12("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
9496
10689
  const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
9497
10690
  const behaviourDecision = decideSenderPolicyRestart({
9498
10691
  previousHash: prevBehaviourHash,
@@ -9545,7 +10738,7 @@ async function processAgent(agent, agentStates) {
9545
10738
  const slackBehaviourSubset = extractSlackBehaviourSubset(
9546
10739
  slackEntry?.config
9547
10740
  );
9548
- const slackBehaviourHash = createHash11("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
10741
+ const slackBehaviourHash = createHash12("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
9549
10742
  const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
9550
10743
  const slackBehaviourDecision = decideSenderPolicyRestart({
9551
10744
  previousHash: prevSlackBehaviourHash,
@@ -9595,24 +10788,24 @@ async function processAgent(agent, agentStates) {
9595
10788
  if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
9596
10789
  try {
9597
10790
  const agentProvisionDir = agentDir;
9598
- const projectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
9599
- mkdirSync7(agentProvisionDir, { recursive: true });
9600
- mkdirSync7(projectDir, { recursive: true });
9601
- const provisionMcpPath = join19(agentProvisionDir, ".mcp.json");
9602
- 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");
9603
10796
  let mcpConfig = { mcpServers: {} };
9604
10797
  try {
9605
- mcpConfig = JSON.parse(readFileSync16(provisionMcpPath, "utf-8"));
10798
+ mcpConfig = JSON.parse(readFileSync17(provisionMcpPath, "utf-8"));
9606
10799
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
9607
10800
  } catch {
9608
10801
  }
9609
- const localDirectChatChannel = join19(homedir10(), ".augmented", "_mcp", "direct-chat-channel.js");
10802
+ const localDirectChatChannel = join20(homedir11(), ".augmented", "_mcp", "direct-chat-channel.js");
9610
10803
  const directChatTeamSettings = refreshData.team?.settings;
9611
10804
  const directChatTz = (() => {
9612
10805
  const tz = directChatTeamSettings?.["timezone"];
9613
10806
  return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
9614
10807
  })();
9615
- if (existsSync10(localDirectChatChannel)) {
10808
+ if (existsSync11(localDirectChatChannel)) {
9616
10809
  const directChatEnv = {
9617
10810
  AGT_HOST: requireHost(),
9618
10811
  // ENG-5901 Track D: templated — the manager exports the real
@@ -9632,7 +10825,7 @@ async function processAgent(agent, agentStates) {
9632
10825
  // ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
9633
10826
  // returns the agent root (NOT the /provision subdir `agentDir` points at),
9634
10827
  // so it byte-matches the broker readers' path.
9635
- AGT_TURN_INITIATOR_FILE: join19(
10828
+ AGT_TURN_INITIATOR_FILE: join20(
9636
10829
  frameworkAdapter.getAgentDir(agent.code_name),
9637
10830
  ".current-turn-initiator.json"
9638
10831
  )
@@ -9652,8 +10845,8 @@ async function processAgent(agent, agentStates) {
9652
10845
  log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
9653
10846
  }
9654
10847
  }
9655
- const staleChannelsPath = join19(projectDir, ".mcp-channels.json");
9656
- if (existsSync10(staleChannelsPath)) {
10848
+ const staleChannelsPath = join20(projectDir, ".mcp-channels.json");
10849
+ if (existsSync11(staleChannelsPath)) {
9657
10850
  try {
9658
10851
  rmSync4(staleChannelsPath, { force: true });
9659
10852
  } catch {
@@ -9722,9 +10915,27 @@ async function processAgent(agent, agentStates) {
9722
10915
  log(`OAuth token refresh failed for '${agent.code_name}/${integration.definition_id}': ${err.message}`);
9723
10916
  }
9724
10917
  }
10918
+ const anchorIntegration = integrations.find((i) => i.definition_id === "anchor-browser");
10919
+ if (anchorIntegration) {
10920
+ const profileName = anchorIntegration.config?.["profile_name"];
10921
+ const apiKey = anchorIntegration.credentials?.["api_key"];
10922
+ const sessionId = await anchorSessionLifecycle.ensure({
10923
+ agentId: agent.agent_id,
10924
+ profileName,
10925
+ apiKey,
10926
+ now: Date.now(),
10927
+ log
10928
+ });
10929
+ const config2 = { ...anchorIntegration.config ?? {} };
10930
+ if (sessionId) config2["session_id"] = sessionId;
10931
+ else delete config2["session_id"];
10932
+ anchorIntegration.config = config2;
10933
+ } else {
10934
+ anchorSessionLifecycle.forget(agent.agent_id, void 0, log);
10935
+ }
9725
10936
  if (hostFlagStore().getBoolean("connectivity-probe")) {
9726
10937
  try {
9727
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
10938
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9728
10939
  await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
9729
10940
  } catch (err) {
9730
10941
  log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
@@ -9735,7 +10946,7 @@ async function processAgent(agent, agentStates) {
9735
10946
  const forceDue = attemptsLeft > 0;
9736
10947
  let probeRan = false;
9737
10948
  try {
9738
- const probeProjectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
10949
+ const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
9739
10950
  probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
9740
10951
  } catch (err) {
9741
10952
  log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
@@ -9748,9 +10959,9 @@ async function processAgent(agent, agentStates) {
9748
10959
  }
9749
10960
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
9750
10961
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
9751
- if (mcpPath && existsSync10(mcpPath)) {
10962
+ if (mcpPath && existsSync11(mcpPath)) {
9752
10963
  try {
9753
- const cfg = JSON.parse(readFileSync16(mcpPath, "utf-8"));
10964
+ const cfg = JSON.parse(readFileSync17(mcpPath, "utf-8"));
9754
10965
  const expectedRemoteKeys = new Set(
9755
10966
  integrations.map((i) => i.definition_id)
9756
10967
  );
@@ -9775,11 +10986,11 @@ async function processAgent(agent, agentStates) {
9775
10986
  recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
9776
10987
  }
9777
10988
  if (intHash !== prevIntHash) {
9778
- const projectDir = join19(homedir10(), ".augmented", agent.code_name, "project");
9779
- const envIntPath = join19(projectDir, ".env.integrations");
10989
+ const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
10990
+ const envIntPath = join20(projectDir, ".env.integrations");
9780
10991
  let preWriteEnv;
9781
10992
  try {
9782
- preWriteEnv = readFileSync16(envIntPath, "utf-8");
10993
+ preWriteEnv = readFileSync17(envIntPath, "utf-8");
9783
10994
  } catch {
9784
10995
  preWriteEnv = void 0;
9785
10996
  }
@@ -9791,9 +11002,9 @@ async function processAgent(agent, agentStates) {
9791
11002
  let rotationHandled = true;
9792
11003
  if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
9793
11004
  try {
9794
- const projectMcpPath = join19(projectDir, ".mcp.json");
9795
- const postWriteEnv = readFileSync16(envIntPath, "utf-8");
9796
- 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");
9797
11008
  const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
9798
11009
  const mcpJsonForReap = JSON.parse(mcpContent);
9799
11010
  const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
@@ -9809,7 +11020,8 @@ async function processAgent(agent, agentStates) {
9809
11020
  isolated: isolationMode(agent.code_name) === "docker"
9810
11021
  });
9811
11022
  }
9812
- const respawnVars = envOnlyRespawnVars(changedVars, mcpJsonForReap, CHANNEL_SECRET_ENV_KEYS);
11023
+ const liveProxyVars = liveProxyExtraHeaderVars(mcpJsonForReap);
11024
+ const respawnVars = envOnlyRespawnVars(changedVars, mcpJsonForReap, CHANNEL_SECRET_ENV_KEYS).filter((v) => !liveProxyVars.has(v));
9813
11025
  const envDiff = classifyEnvIntegrationsDiff(preWriteEnv, postWriteEnv);
9814
11026
  const rotatedOnlyVars = new Set(envDiff.rotated);
9815
11027
  const envRespawnReason = respawnVars.length > 0 && respawnVars.every((v) => rotatedOnlyVars.has(v)) ? "credential-rotation" : "hot-reload-mcp";
@@ -9869,10 +11081,10 @@ async function processAgent(agent, agentStates) {
9869
11081
  desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
9870
11082
  }
9871
11083
  const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
9872
- 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);
9873
11085
  return `${e.serverId}|${e.url}|${headersHash}`;
9874
11086
  }).join("\n");
9875
- const mcpHash = createHash11("sha256").update(hashBasis).digest("hex").slice(0, 16);
11087
+ const mcpHash = createHash12("sha256").update(hashBasis).digest("hex").slice(0, 16);
9876
11088
  const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
9877
11089
  const structureHash = managedMcpStructureHash(desiredEntries);
9878
11090
  const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
@@ -9887,15 +11099,15 @@ async function processAgent(agent, agentStates) {
9887
11099
  if (mcpHash !== prevMcpHash) {
9888
11100
  for (const e of desiredEntries) {
9889
11101
  frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
9890
- 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);
9891
11103
  log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
9892
11104
  }
9893
11105
  if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
9894
11106
  const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
9895
11107
  if (mcpPath) {
9896
11108
  try {
9897
- const { readFileSync: readFileSync17 } = await import("fs");
9898
- const mcpConfig = JSON.parse(readFileSync17(mcpPath, "utf-8"));
11109
+ const { readFileSync: readFileSync18 } = await import("fs");
11110
+ const mcpConfig = JSON.parse(readFileSync18(mcpPath, "utf-8"));
9899
11111
  if (mcpConfig.mcpServers) {
9900
11112
  for (const key of Object.keys(mcpConfig.mcpServers)) {
9901
11113
  if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
@@ -9996,7 +11208,7 @@ async function processAgent(agent, agentStates) {
9996
11208
  if (frameworkAdapter.installSkillFiles) {
9997
11209
  const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
9998
11210
  const installedIntegrationSkills = [];
9999
- const { createHash: createHash12 } = await import("crypto");
11211
+ const { createHash: createHash13 } = await import("crypto");
10000
11212
  const refreshAny = refreshData;
10001
11213
  const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
10002
11214
  const contextBySlug = /* @__PURE__ */ new Map();
@@ -10025,7 +11237,7 @@ async function processAgent(agent, agentStates) {
10025
11237
  )
10026
11238
  }));
10027
11239
  const bundle = buildIntegrationBundle(renderedScopes);
10028
- 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);
10029
11241
  const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
10030
11242
  if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
10031
11243
  frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
@@ -10038,18 +11250,18 @@ async function processAgent(agent, agentStates) {
10038
11250
  }
10039
11251
  try {
10040
11252
  const { readdirSync: readdirSync6, rmSync: rmSync5 } = await import("fs");
10041
- const { homedir: homedir11 } = await import("os");
11253
+ const { homedir: homedir12 } = await import("os");
10042
11254
  const frameworkId2 = frameworkAdapter.id;
10043
11255
  const candidateSkillDirs = [
10044
11256
  // Claude Code — framework runtime tree
10045
- join19(homedir11(), ".augmented", agent.code_name, "skills"),
11257
+ join20(homedir12(), ".augmented", agent.code_name, "skills"),
10046
11258
  // Claude Code — project tree
10047
- join19(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
11259
+ join20(homedir12(), ".augmented", agent.code_name, "project", ".claude", "skills"),
10048
11260
  // Defensive: legacy provision-side path, not currently an
10049
11261
  // install target but cheap to sweep.
10050
- join19(agentDir, ".claude", "skills")
11262
+ join20(agentDir, ".claude", "skills")
10051
11263
  ];
10052
- const existingDirs = candidateSkillDirs.filter((d) => existsSync10(d));
11264
+ const existingDirs = candidateSkillDirs.filter((d) => existsSync11(d));
10053
11265
  const discoveredEntries = /* @__PURE__ */ new Set();
10054
11266
  for (const dir of existingDirs) {
10055
11267
  try {
@@ -10063,8 +11275,8 @@ async function processAgent(agent, agentStates) {
10063
11275
  }
10064
11276
  const removeSkillFolder = (entry, reason) => {
10065
11277
  for (const dir of existingDirs) {
10066
- const p = join19(dir, entry);
10067
- if (existsSync10(p)) {
11278
+ const p = join20(dir, entry);
11279
+ if (existsSync11(p)) {
10068
11280
  rmSync5(p, { recursive: true, force: true });
10069
11281
  }
10070
11282
  }
@@ -10083,7 +11295,7 @@ async function processAgent(agent, agentStates) {
10083
11295
  const sharedSkillsPayload = refreshAny.shared_skills;
10084
11296
  const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
10085
11297
  const manifestPath = managedSkillManifestPath(
10086
- join19(homedir10(), ".augmented", agent.code_name)
11298
+ join20(homedir11(), ".augmented", agent.code_name)
10087
11299
  );
10088
11300
  const prevIds = /* @__PURE__ */ new Set([
10089
11301
  ...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
@@ -10092,7 +11304,7 @@ async function processAgent(agent, agentStates) {
10092
11304
  const plan = planGlobalSkillSync(
10093
11305
  [...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
10094
11306
  prevIds,
10095
- (content) => createHash12("sha256").update(content).digest("hex").slice(0, 12),
11307
+ (content) => createHash13("sha256").update(content).digest("hex").slice(0, 12),
10096
11308
  (skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
10097
11309
  { desiredResolved }
10098
11310
  );
@@ -10103,15 +11315,15 @@ async function processAgent(agent, agentStates) {
10103
11315
  }
10104
11316
  if (plan.removes.length) {
10105
11317
  const globalSkillDirs = [
10106
- join19(homedir10(), ".augmented", agent.code_name, "skills"),
10107
- join19(homedir10(), ".augmented", agent.code_name, "project", ".claude", "skills"),
10108
- 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")
10109
11321
  ];
10110
11322
  for (const id of plan.removes) {
10111
11323
  let prunedAny = false;
10112
11324
  for (const dir of globalSkillDirs) {
10113
- const p = join19(dir, id);
10114
- if (existsSync10(p) && existsSync10(join19(p, "SKILL.md"))) {
11325
+ const p = join20(dir, id);
11326
+ if (existsSync11(p) && existsSync11(join20(p, "SKILL.md"))) {
10115
11327
  rmSync4(p, { recursive: true, force: true });
10116
11328
  prunedAny = true;
10117
11329
  }
@@ -10143,7 +11355,7 @@ async function processAgent(agent, agentStates) {
10143
11355
  const slug = hook.integration_slug ?? hook.plugin_slug;
10144
11356
  if (!slug) continue;
10145
11357
  try {
10146
- 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);
10147
11359
  const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
10148
11360
  if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
10149
11361
  const result = await frameworkAdapter.executePluginHook({
@@ -10158,9 +11370,9 @@ async function processAgent(agent, agentStates) {
10158
11370
  } else if (result.timedOut) {
10159
11371
  log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
10160
11372
  } else {
10161
- 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);
10162
11374
  const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
10163
- 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;
10164
11376
  log(
10165
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}]`
10166
11378
  );
@@ -10314,8 +11526,8 @@ async function processAgent(agent, agentStates) {
10314
11526
  const sess = getSessionState(agent.code_name);
10315
11527
  let mcpJsonParsed = null;
10316
11528
  try {
10317
- const mcpPath = join19(getProjectDir(agent.code_name), ".mcp.json");
10318
- 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"));
10319
11531
  } catch {
10320
11532
  }
10321
11533
  reapMissingMcpSessions({
@@ -10640,10 +11852,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10640
11852
  }
10641
11853
  }
10642
11854
  const trackedFiles = frameworkAdapter.driftTrackedFiles();
10643
- if (trackedFiles.length > 0 && existsSync10(agentDir)) {
11855
+ if (trackedFiles.length > 0 && existsSync11(agentDir)) {
10644
11856
  const hashes = /* @__PURE__ */ new Map();
10645
11857
  for (const file of trackedFiles) {
10646
- const h = hashFile(join19(agentDir, file));
11858
+ const h = hashFile(join20(agentDir, file));
10647
11859
  if (h) hashes.set(file, h);
10648
11860
  }
10649
11861
  agentState.writtenHashes.set(agent.agent_id, hashes);
@@ -10658,7 +11870,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
10658
11870
  refreshData.agent.onboarding_state
10659
11871
  );
10660
11872
  const obStep = obState.step;
10661
- const markerPath = join19(homedir10(), ".augmented", agent.code_name, "onboarding-drive.json");
11873
+ const markerPath = join20(homedir11(), ".augmented", agent.code_name, "onboarding-drive.json");
10662
11874
  const marker = readOnboardingDriveMarker(markerPath);
10663
11875
  const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
10664
11876
  if (decision.clearMarker) {
@@ -10719,11 +11931,55 @@ function deriveEgressAllowlist(toolsRaw) {
10719
11931
  );
10720
11932
  }
10721
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
+ }
10722
11978
  async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10723
11979
  const codeName = agent.code_name;
10724
11980
  const projectDir = getProjectDir(codeName);
10725
- const mcpConfigPath = join19(projectDir, ".mcp.json");
10726
- const claudeMdPath = join19(projectDir, "CLAUDE.md");
11981
+ const mcpConfigPath = join20(projectDir, ".mcp.json");
11982
+ const claudeMdPath = join20(projectDir, "CLAUDE.md");
10727
11983
  if (restartBreaker.isTripped(codeName)) {
10728
11984
  const trip = restartBreaker.getTrip(codeName);
10729
11985
  return {
@@ -10745,6 +12001,10 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10745
12001
  }
10746
12002
  return resolveAgentTimezone(ownTz, teamTz, orgTierTz);
10747
12003
  })();
12004
+ const frameworkId = agentFrameworkCache.get(codeName) ?? DEFAULT_FRAMEWORK;
12005
+ if (frameworkId === "opencode") {
12006
+ return ensureOpencodeRuntime(agent, refreshData, agentTimezone ?? null);
12007
+ }
10748
12008
  const channelConfigs = refreshData.channel_configs;
10749
12009
  const { devChannels, pluginChannels } = resolveChannelLaunchFlags(
10750
12010
  channelConfigs,
@@ -10877,7 +12137,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
10877
12137
  const ctx = getLastFailureContext(codeName);
10878
12138
  const recovery = prepareForRespawn(codeName);
10879
12139
  const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
10880
- ${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)`;
10881
12141
  const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
10882
12142
  const recoverySummary = recovery ? `; recovery=${recovery}` : "";
10883
12143
  log(
@@ -10891,7 +12151,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
10891
12151
  );
10892
12152
  getHostId().then((hostId) => {
10893
12153
  if (!hostId) return;
10894
- 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;
10895
12155
  return api.post("/host/events", {
10896
12156
  host_id: hostId,
10897
12157
  agent_code_name: codeName,
@@ -11040,7 +12300,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
11040
12300
  if (!claudeAuthTupleBySession.has(codeName)) {
11041
12301
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
11042
12302
  }
11043
- 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);
11044
12304
  const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
11045
12305
  if (stableTasksHash !== prevHash) {
11046
12306
  const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
@@ -11257,7 +12517,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
11257
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}`));
11258
12518
  void (async () => {
11259
12519
  try {
11260
- const { collectDiagnostics } = await import("../persistent-session-VSSEZTY7.js");
12520
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11261
12521
  await api.post("/host/heartbeat", {
11262
12522
  host_id: hostId,
11263
12523
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11307,7 +12567,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
11307
12567
  }
11308
12568
  try {
11309
12569
  const hostId = await getHostId();
11310
- const { collectDiagnostics } = await import("../persistent-session-VSSEZTY7.js");
12570
+ const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
11311
12571
  await api.post("/host/heartbeat", {
11312
12572
  host_id: hostId,
11313
12573
  agent_diagnostics: collectDiagnostics([codeName])
@@ -11528,6 +12788,46 @@ async function pollDirectChatMessages(agentStates) {
11528
12788
  }
11529
12789
  }
11530
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
+ }
11531
12831
  async function processDirectChatMessage(agent, msg) {
11532
12832
  noteInbound(agent.codeName);
11533
12833
  const fw = agentFrameworkCache.get(agent.codeName) ?? DEFAULT_FRAMEWORK;
@@ -11544,12 +12844,16 @@ async function processDirectChatMessage(agent, msg) {
11544
12844
  }
11545
12845
  return;
11546
12846
  }
12847
+ if (fw === "opencode") {
12848
+ await processDirectChatMessageOpencode(agent, msg);
12849
+ return;
12850
+ }
11547
12851
  if (isSessionHealthy(agent.codeName)) {
11548
12852
  const useDoorbell = hostFlagStore().getBoolean("direct-chat-doorbell") || isolationMode(agent.codeName) === "docker";
11549
12853
  if (useDoorbell) {
11550
12854
  try {
11551
- const doorbell = directChatDoorbellPath(agent.agentId, homedir10());
11552
- mkdirSync7(dirname6(doorbell), { recursive: true });
12855
+ const doorbell = directChatDoorbellPath(agent.agentId, homedir11());
12856
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11553
12857
  writeFileSync8(doorbell, String(Date.now()));
11554
12858
  log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
11555
12859
  return;
@@ -11637,8 +12941,8 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
11637
12941
  }
11638
12942
  if (run_id) openInjectedRunByCode.set(codeName, run_id);
11639
12943
  try {
11640
- const doorbell = directChatDoorbellPath(agentId, homedir10());
11641
- mkdirSync7(dirname6(doorbell), { recursive: true });
12944
+ const doorbell = directChatDoorbellPath(agentId, homedir11());
12945
+ mkdirSync8(dirname6(doorbell), { recursive: true });
11642
12946
  writeFileSync8(doorbell, String(Date.now()));
11643
12947
  } catch (err) {
11644
12948
  log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
@@ -11731,7 +13035,7 @@ async function processClaudePairSessions(agents) {
11731
13035
  killPairSession,
11732
13036
  pairTmuxSession,
11733
13037
  finalizeClaudePairOnboarding
11734
- } = await import("../claude-pair-runtime-PWVOBBX4.js");
13038
+ } = await import("../claude-pair-runtime-DO6OWWGD.js");
11735
13039
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
11736
13040
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
11737
13041
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -11766,12 +13070,12 @@ async function processClaudePairSessions(agents) {
11766
13070
  try {
11767
13071
  if (session.status === "initiating") {
11768
13072
  log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);
11769
- const spawn = await spawnPairSession(pairSession);
11770
- if (!spawn.ok) {
13073
+ const spawn2 = await spawnPairSession(pairSession);
13074
+ if (!spawn2.ok) {
11771
13075
  await reportAndCleanup(session.pair_id, {
11772
13076
  status: "failure",
11773
- error_code: spawn.error.kind,
11774
- 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
11775
13079
  });
11776
13080
  continue;
11777
13081
  }
@@ -11974,8 +13278,8 @@ function parseMemoryFile(raw, fallbackName) {
11974
13278
  };
11975
13279
  }
11976
13280
  async function syncMemories(agent, configDir, log2) {
11977
- const projectDir = join19(configDir, agent.code_name, "project");
11978
- const memoryDir = join19(projectDir, "memory");
13281
+ const projectDir = join20(configDir, agent.code_name, "project");
13282
+ const memoryDir = join20(projectDir, "memory");
11979
13283
  const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
11980
13284
  if (isFreshSync) {
11981
13285
  log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
@@ -11986,15 +13290,15 @@ async function syncMemories(agent, configDir, log2) {
11986
13290
  }
11987
13291
  pendingFreshMemorySync.delete(agent.agent_id);
11988
13292
  }
11989
- if (existsSync10(memoryDir)) {
13293
+ if (existsSync11(memoryDir)) {
11990
13294
  const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
11991
13295
  const currentHashes = /* @__PURE__ */ new Map();
11992
13296
  const changedMemories = [];
11993
13297
  for (const file of readdirSync5(memoryDir)) {
11994
13298
  if (!file.endsWith(".md")) continue;
11995
13299
  try {
11996
- const raw = readFileSync16(join19(memoryDir, file), "utf-8");
11997
- 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);
11998
13302
  currentHashes.set(file, fileHash);
11999
13303
  if (prevHashes.get(file) === fileHash) continue;
12000
13304
  const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
@@ -12018,7 +13322,7 @@ async function syncMemories(agent, configDir, log2) {
12018
13322
  } catch (err) {
12019
13323
  for (const mem of changedMemories) {
12020
13324
  for (const [file] of currentHashes) {
12021
- 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$/, ""));
12022
13326
  if (parsed?.name === mem.name) currentHashes.delete(file);
12023
13327
  }
12024
13328
  }
@@ -12031,29 +13335,29 @@ async function syncMemories(agent, configDir, log2) {
12031
13335
  }
12032
13336
  }
12033
13337
  async function downloadMemories(agent, memoryDir, log2, { force }) {
12034
- const localFiles = existsSync10(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
12035
- 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);
12036
13340
  const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
12037
13341
  const prevDownload = lastDownloadHash.get(agent.agent_id);
12038
13342
  try {
12039
13343
  const dbMemories = await api.post("/host/memories", {
12040
13344
  agent_id: agent.agent_id
12041
13345
  });
12042
- 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);
12043
13347
  if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
12044
13348
  return true;
12045
13349
  }
12046
13350
  lastDownloadHash.set(agent.agent_id, responseHash);
12047
13351
  lastLocalFileHash.set(agent.agent_id, localListHash);
12048
13352
  if (dbMemories.memories?.length) {
12049
- mkdirSync7(memoryDir, { recursive: true });
13353
+ mkdirSync8(memoryDir, { recursive: true });
12050
13354
  let written = 0;
12051
13355
  let overwritten = 0;
12052
13356
  for (let i = 0; i < dbMemories.memories.length; i++) {
12053
13357
  const mem = dbMemories.memories[i];
12054
13358
  const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
12055
13359
  const slug = rawSlug || `memory-${i}`;
12056
- const filePath = join19(memoryDir, `${slug}.md`);
13360
+ const filePath = join20(memoryDir, `${slug}.md`);
12057
13361
  const desired = `---
12058
13362
  name: ${JSON.stringify(mem.name)}
12059
13363
  type: ${mem.type}
@@ -12062,10 +13366,10 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
12062
13366
 
12063
13367
  ${mem.content}
12064
13368
  `;
12065
- if (existsSync10(filePath)) {
13369
+ if (existsSync11(filePath)) {
12066
13370
  let existing = "";
12067
13371
  try {
12068
- existing = readFileSync16(filePath, "utf-8");
13372
+ existing = readFileSync17(filePath, "utf-8");
12069
13373
  } catch {
12070
13374
  }
12071
13375
  if (existing === desired) continue;
@@ -12078,7 +13382,7 @@ ${mem.content}
12078
13382
  }
12079
13383
  if (written > 0 || overwritten > 0) {
12080
13384
  const updatedFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort();
12081
- 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));
12082
13386
  log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
12083
13387
  }
12084
13388
  }
@@ -12089,7 +13393,7 @@ ${mem.content}
12089
13393
  }
12090
13394
  }
12091
13395
  async function cleanupAgentFiles(codeName, agentDir) {
12092
- if (existsSync10(agentDir)) {
13396
+ if (existsSync11(agentDir)) {
12093
13397
  try {
12094
13398
  rmSync4(agentDir, { recursive: true, force: true });
12095
13399
  log(`Removed provision directory for '${codeName}'`);
@@ -12113,8 +13417,8 @@ var caffeinateProc = null;
12113
13417
  async function startCaffeinate() {
12114
13418
  if (process.platform !== "darwin") return;
12115
13419
  try {
12116
- const { spawn } = await import("child_process");
12117
- caffeinateProc = spawn("caffeinate", ["-dims"], {
13420
+ const { spawn: spawn2 } = await import("child_process");
13421
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
12118
13422
  stdio: "ignore",
12119
13423
  detached: false
12120
13424
  });
@@ -12261,8 +13565,8 @@ async function killAllAgtTmuxSessions() {
12261
13565
  ["list-sessions", "-F", "#{session_name}"],
12262
13566
  { timeout: 2e3, stdin: "ignore" }
12263
13567
  );
12264
- const sessions = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
12265
- for (const session of sessions) {
13568
+ const sessions2 = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
13569
+ for (const session of sessions2) {
12266
13570
  try {
12267
13571
  await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
12268
13572
  timeout: 2e3,
@@ -12272,8 +13576,8 @@ async function killAllAgtTmuxSessions() {
12272
13576
  } catch {
12273
13577
  }
12274
13578
  }
12275
- if (sessions.length > 0) {
12276
- log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);
13579
+ if (sessions2.length > 0) {
13580
+ log(`Cleaned up ${sessions2.length} agt-* tmux session(s)`);
12277
13581
  }
12278
13582
  } catch {
12279
13583
  }
@@ -12309,6 +13613,7 @@ async function stopPolling(opts = {}) {
12309
13613
  const subsysStartedAt = Date.now();
12310
13614
  stopCaffeinate();
12311
13615
  stopRealtimeChat();
13616
+ stopAllOpencodeSlackIngests(log);
12312
13617
  log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
12313
13618
  for (const codeName of [...scheduledRunsByCode.keys()]) {
12314
13619
  closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
@@ -12323,8 +13628,8 @@ function startManager(opts) {
12323
13628
  config = opts;
12324
13629
  try {
12325
13630
  const stateFile = getStateFile();
12326
- if (existsSync10(stateFile)) {
12327
- const raw = readFileSync16(stateFile, "utf-8");
13631
+ if (existsSync11(stateFile)) {
13632
+ const raw = readFileSync17(stateFile, "utf-8");
12328
13633
  const parsed = JSON.parse(raw);
12329
13634
  if (Array.isArray(parsed.agents)) {
12330
13635
  state6.agents = parsed.agents;
@@ -12351,7 +13656,7 @@ function startManager(opts) {
12351
13656
  log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
12352
13657
  }
12353
13658
  log(
12354
- `[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")}`
12355
13660
  );
12356
13661
  deployMcpAssets();
12357
13662
  reapOrphanChannelMcps({ log });
@@ -12372,7 +13677,7 @@ async function reapOrphanedClaudePids() {
12372
13677
  const looksLikeClaude = (pid) => {
12373
13678
  if (process.platform !== "linux") return true;
12374
13679
  try {
12375
- const comm = readFileSync16(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
13680
+ const comm = readFileSync17(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
12376
13681
  return comm.includes("claude");
12377
13682
  } catch {
12378
13683
  return false;
@@ -12390,16 +13695,16 @@ async function reapOrphanedClaudePids() {
12390
13695
  },
12391
13696
  looksLikeClaude
12392
13697
  );
12393
- for (const spawn of decision.toKill) {
13698
+ for (const spawn2 of decision.toKill) {
12394
13699
  try {
12395
- process.kill(spawn.pid, "SIGKILL");
13700
+ process.kill(spawn2.pid, "SIGKILL");
12396
13701
  } catch (err) {
12397
- log(`[drain] reaper SIGKILL pid=${spawn.pid} failed: ${err.message}`);
13702
+ log(`[drain] reaper SIGKILL pid=${spawn2.pid} failed: ${err.message}`);
12398
13703
  }
12399
13704
  }
12400
- for (const spawn of decision.pidReusedSkipped) {
13705
+ for (const spawn2 of decision.pidReusedSkipped) {
12401
13706
  log(
12402
- `[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)`
12403
13708
  );
12404
13709
  }
12405
13710
  log(formatReaperBootLine({
@@ -12469,14 +13774,14 @@ function restartRunningChannelMcps(basenames) {
12469
13774
  }
12470
13775
  }
12471
13776
  function deployMcpAssets() {
12472
- const targetDir = join19(homedir10(), ".augmented", "_mcp");
12473
- mkdirSync7(targetDir, { recursive: true });
13777
+ const targetDir = join20(homedir11(), ".augmented", "_mcp");
13778
+ mkdirSync8(targetDir, { recursive: true });
12474
13779
  const moduleDir = dirname6(fileURLToPath(import.meta.url));
12475
13780
  let mcpSourceDir = "";
12476
13781
  let dir = moduleDir;
12477
13782
  for (let i = 0; i < 6; i++) {
12478
- const candidate = join19(dir, "dist", "mcp");
12479
- if (existsSync10(join19(candidate, "index.js"))) {
13783
+ const candidate = join20(dir, "dist", "mcp");
13784
+ if (existsSync11(join20(candidate, "index.js"))) {
12480
13785
  mcpSourceDir = candidate;
12481
13786
  break;
12482
13787
  }
@@ -12491,8 +13796,8 @@ function deployMcpAssets() {
12491
13796
  const changedBasenames = [];
12492
13797
  const fileHash = (p) => {
12493
13798
  try {
12494
- if (!existsSync10(p)) return null;
12495
- return createHash11("sha256").update(readFileSync16(p)).digest("hex");
13799
+ if (!existsSync11(p)) return null;
13800
+ return createHash12("sha256").update(readFileSync17(p)).digest("hex");
12496
13801
  } catch {
12497
13802
  return null;
12498
13803
  }
@@ -12556,9 +13861,9 @@ function deployMcpAssets() {
12556
13861
  // needs restarting to pick up a token rotation.
12557
13862
  "xero.js"
12558
13863
  ]) {
12559
- const src = join19(mcpSourceDir, file);
12560
- const dst = join19(targetDir, file);
12561
- if (!existsSync10(src)) continue;
13864
+ const src = join20(mcpSourceDir, file);
13865
+ const dst = join20(targetDir, file);
13866
+ if (!existsSync11(src)) continue;
12562
13867
  const before = fileHash(dst);
12563
13868
  try {
12564
13869
  copyFileSync(src, dst);
@@ -12575,16 +13880,16 @@ function deployMcpAssets() {
12575
13880
  log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
12576
13881
  restartRunningChannelMcps(changedBasenames);
12577
13882
  }
12578
- const localMcpPath = join19(targetDir, "index.js");
13883
+ const localMcpPath = join20(targetDir, "index.js");
12579
13884
  try {
12580
- const agentsDir = join19(homedir10(), ".augmented", "agents");
12581
- if (existsSync10(agentsDir)) {
13885
+ const agentsDir = join20(homedir11(), ".augmented", "agents");
13886
+ if (existsSync11(agentsDir)) {
12582
13887
  for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
12583
13888
  if (!entry.isDirectory()) continue;
12584
13889
  for (const subdir of ["provision", "project"]) {
12585
- const mcpJsonPath = join19(agentsDir, entry.name, subdir, ".mcp.json");
13890
+ const mcpJsonPath = join20(agentsDir, entry.name, subdir, ".mcp.json");
12586
13891
  try {
12587
- const raw = readFileSync16(mcpJsonPath, "utf-8");
13892
+ const raw = readFileSync17(mcpJsonPath, "utf-8");
12588
13893
  if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
12589
13894
  const mcpConfig = JSON.parse(raw);
12590
13895
  const augServer = mcpConfig.mcpServers?.["augmented"];