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