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