@integrity-labs/agt-cli 0.28.318 → 0.28.320
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +96 -4
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-2DIWTRTU.js → chunk-CJSATK6B.js} +1024 -200
- package/dist/chunk-CJSATK6B.js.map +1 -0
- package/dist/{chunk-4DHYHNCV.js → chunk-NAS4DZNG.js} +184 -2
- package/dist/chunk-NAS4DZNG.js.map +1 -0
- package/dist/{claude-pair-runtime-PWVOBBX4.js → claude-pair-runtime-DO6OWWGD.js} +2 -2
- package/dist/lib/manager-worker.js +1505 -200
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +1 -1
- package/dist/mcp/origami.js +6 -1
- package/dist/mcp/remote-oauth-proxy.js +36 -10
- 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-VSSEZTY7.js → persistent-session-ZQSAZIVM.js} +2 -2
- package/dist/{responsiveness-probe-I6YFUKYD.js → responsiveness-probe-NARLJJGH.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-2DIWTRTU.js.map +0 -1
- package/dist/chunk-4DHYHNCV.js.map +0 -1
- /package/dist/{claude-pair-runtime-PWVOBBX4.js.map → claude-pair-runtime-DO6OWWGD.js.map} +0 -0
- /package/dist/{persistent-session-VSSEZTY7.js.map → persistent-session-ZQSAZIVM.js.map} +0 -0
- /package/dist/{responsiveness-probe-I6YFUKYD.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,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
getCachedClaudeAuthMode,
|
|
27
29
|
getHostId,
|
|
28
30
|
givenUpMcpServerKeys,
|
|
31
|
+
liveProxyExtraHeaderVars,
|
|
29
32
|
provision,
|
|
30
33
|
provisionAutoKanbanProgressHook,
|
|
31
34
|
provisionChannelProgressHook,
|
|
@@ -34,12 +37,13 @@ import {
|
|
|
34
37
|
provisionPreCompactHook,
|
|
35
38
|
provisionSessionStateHook,
|
|
36
39
|
provisionStopHook,
|
|
40
|
+
readChannelServerEnv,
|
|
37
41
|
reapMissingMcpSessions,
|
|
38
42
|
reapStaleMcpChildren,
|
|
39
43
|
requireHost,
|
|
40
44
|
safeWriteJsonAtomic,
|
|
41
45
|
setConfigHash
|
|
42
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-CJSATK6B.js";
|
|
43
47
|
import {
|
|
44
48
|
getProjectDir as getProjectDir2,
|
|
45
49
|
getReadyTasks,
|
|
@@ -48,6 +52,7 @@ import {
|
|
|
48
52
|
syncTasksToScheduler
|
|
49
53
|
} from "../chunk-I3YS5WFV.js";
|
|
50
54
|
import {
|
|
55
|
+
AnchorSessionClient,
|
|
51
56
|
CONVERSATION_FAILURE_CATEGORIES,
|
|
52
57
|
DEFAULT_FRAMEWORK,
|
|
53
58
|
FLAGS_SCHEMA_VERSION,
|
|
@@ -127,17 +132,17 @@ import {
|
|
|
127
132
|
takeZombieDetection,
|
|
128
133
|
transcriptActivityAgeSeconds,
|
|
129
134
|
writeEgressAllowlist
|
|
130
|
-
} from "../chunk-
|
|
135
|
+
} from "../chunk-NAS4DZNG.js";
|
|
131
136
|
import {
|
|
132
137
|
reapOrphanChannelMcps
|
|
133
138
|
} from "../chunk-XWVM4KPK.js";
|
|
134
139
|
|
|
135
140
|
// src/lib/manager-worker.ts
|
|
136
|
-
import { createHash as
|
|
137
|
-
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";
|
|
138
143
|
import { execFileSync as syncExecFile } from "child_process";
|
|
139
|
-
import { join as
|
|
140
|
-
import { homedir as
|
|
144
|
+
import { join as join20, dirname as dirname6 } from "path";
|
|
145
|
+
import { homedir as homedir11 } from "os";
|
|
141
146
|
import { fileURLToPath } from "url";
|
|
142
147
|
|
|
143
148
|
// src/lib/claude-code-upgrade-throttle.ts
|
|
@@ -279,6 +284,95 @@ function withResolvedRemoteMcp(integration) {
|
|
|
279
284
|
};
|
|
280
285
|
}
|
|
281
286
|
|
|
287
|
+
// src/lib/anchor-session-lifecycle.ts
|
|
288
|
+
var DEFAULT_ANCHOR_SESSION_MAX_DURATION_MINUTES = 60;
|
|
289
|
+
var DEFAULT_ANCHOR_SESSION_REFRESH_MARGIN_MINUTES = 5;
|
|
290
|
+
var AnchorSessionLifecycle = class {
|
|
291
|
+
store = /* @__PURE__ */ new Map();
|
|
292
|
+
maxDurationMs;
|
|
293
|
+
refreshMarginMs;
|
|
294
|
+
idleTimeoutMinutes;
|
|
295
|
+
createClient;
|
|
296
|
+
constructor(opts = {}) {
|
|
297
|
+
const maxDurationMinutes = opts.maxDurationMinutes ?? DEFAULT_ANCHOR_SESSION_MAX_DURATION_MINUTES;
|
|
298
|
+
this.maxDurationMs = maxDurationMinutes * 6e4;
|
|
299
|
+
this.refreshMarginMs = (opts.refreshMarginMinutes ?? DEFAULT_ANCHOR_SESSION_REFRESH_MARGIN_MINUTES) * 6e4;
|
|
300
|
+
this.idleTimeoutMinutes = opts.idleTimeoutMinutes ?? maxDurationMinutes;
|
|
301
|
+
this.createClient = opts.createClient ?? ((apiKey) => new AnchorSessionClient({ apiKey }));
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Return the session id the agent should bind, minting or re-minting as needed.
|
|
305
|
+
* Returns null for the stateless case (no profile / no key / mint failure with
|
|
306
|
+
* no valid fallback) - the manager then leaves `ANCHOR_BROWSER_SESSION_ID`
|
|
307
|
+
* empty. Never throws.
|
|
308
|
+
*/
|
|
309
|
+
async ensure(params) {
|
|
310
|
+
const { agentId, now } = params;
|
|
311
|
+
const log2 = params.log ?? (() => {
|
|
312
|
+
});
|
|
313
|
+
const profileName = params.profileName?.trim() || "";
|
|
314
|
+
const apiKey = params.apiKey?.trim() || "";
|
|
315
|
+
const existing = this.store.get(agentId);
|
|
316
|
+
if (!profileName) {
|
|
317
|
+
if (existing) {
|
|
318
|
+
this.endBestEffort(apiKey, existing.sessionId, log2);
|
|
319
|
+
this.store.delete(agentId);
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (!apiKey) {
|
|
324
|
+
if (existing && existing.profileName === profileName && now < existing.expiresAt) {
|
|
325
|
+
return existing.sessionId;
|
|
326
|
+
}
|
|
327
|
+
log2(`[anchor-session] ${agentId}: profile "${profileName}" but no api key - stateless`);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
if (existing && existing.profileName === profileName && now < existing.expiresAt - this.refreshMarginMs) {
|
|
331
|
+
return existing.sessionId;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const client2 = this.createClient(apiKey);
|
|
335
|
+
const session = await client2.createSession({
|
|
336
|
+
profileName,
|
|
337
|
+
dedicatedStickyIp: true,
|
|
338
|
+
maxDurationMinutes: this.maxDurationMs / 6e4,
|
|
339
|
+
idleTimeoutMinutes: this.idleTimeoutMinutes
|
|
340
|
+
});
|
|
341
|
+
const effectiveMs = Math.min(this.maxDurationMs, this.idleTimeoutMinutes * 6e4);
|
|
342
|
+
this.store.set(agentId, {
|
|
343
|
+
sessionId: session.sessionId,
|
|
344
|
+
profileName,
|
|
345
|
+
expiresAt: now + effectiveMs
|
|
346
|
+
});
|
|
347
|
+
if (existing && existing.sessionId !== session.sessionId) {
|
|
348
|
+
this.endBestEffort(apiKey, existing.sessionId, log2);
|
|
349
|
+
}
|
|
350
|
+
log2(`[anchor-session] ${agentId}: minted session for profile "${profileName}"`);
|
|
351
|
+
return session.sessionId;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
354
|
+
if (existing && existing.profileName === profileName && now < existing.expiresAt) {
|
|
355
|
+
log2(`[anchor-session] ${agentId}: re-mint failed (${reason}) - keeping current session`);
|
|
356
|
+
return existing.sessionId;
|
|
357
|
+
}
|
|
358
|
+
log2(`[anchor-session] ${agentId}: mint failed (${reason}) - degrading to stateless`);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Forget an agent's session (e.g. on decommission). Best-effort ends it. */
|
|
363
|
+
forget(agentId, apiKey, log2 = () => {
|
|
364
|
+
}) {
|
|
365
|
+
const existing = this.store.get(agentId);
|
|
366
|
+
if (!existing) return;
|
|
367
|
+
this.endBestEffort(apiKey?.trim() || "", existing.sessionId, log2);
|
|
368
|
+
this.store.delete(agentId);
|
|
369
|
+
}
|
|
370
|
+
endBestEffort(apiKey, sessionId, log2) {
|
|
371
|
+
if (!apiKey || !sessionId) return;
|
|
372
|
+
void this.createClient(apiKey).endSession(sessionId).catch((err) => log2(`[anchor-session] failed to end a stale session: ${err.message}`));
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
282
376
|
// src/lib/channel-restart-decision.ts
|
|
283
377
|
function launchableChannelIds(channelConfigs) {
|
|
284
378
|
if (!channelConfigs) return /* @__PURE__ */ new Set();
|
|
@@ -5347,6 +5441,1103 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
5347
5441
|
}
|
|
5348
5442
|
}
|
|
5349
5443
|
|
|
5444
|
+
// src/lib/opencode-session.ts
|
|
5445
|
+
import { spawn, execSync } from "child_process";
|
|
5446
|
+
import { createServer } from "net";
|
|
5447
|
+
import { randomBytes } from "crypto";
|
|
5448
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync15 } from "fs";
|
|
5449
|
+
import { homedir as homedir9, userInfo } from "os";
|
|
5450
|
+
import { join as join18 } from "path";
|
|
5451
|
+
var OPENCODE_BIN = process.env["AGT_OPENCODE_BIN"]?.trim() || "opencode";
|
|
5452
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
5453
|
+
var bridges = /* @__PURE__ */ new Map();
|
|
5454
|
+
function opencodeTmuxSession(codeName) {
|
|
5455
|
+
return `agt-oc-${codeName}`;
|
|
5456
|
+
}
|
|
5457
|
+
function opencodePaneLogPath(codeName) {
|
|
5458
|
+
return join18(homedir9(), ".augmented", codeName, "opencode-serve.log");
|
|
5459
|
+
}
|
|
5460
|
+
function baseUrlFor(port) {
|
|
5461
|
+
return `http://127.0.0.1:${port}`;
|
|
5462
|
+
}
|
|
5463
|
+
function findFreePort() {
|
|
5464
|
+
return new Promise((resolve, reject) => {
|
|
5465
|
+
const srv = createServer();
|
|
5466
|
+
srv.on("error", reject);
|
|
5467
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
5468
|
+
const addr = srv.address();
|
|
5469
|
+
if (addr && typeof addr === "object") {
|
|
5470
|
+
const { port } = addr;
|
|
5471
|
+
srv.close(() => resolve(port));
|
|
5472
|
+
} else {
|
|
5473
|
+
srv.close(() => reject(new Error("could not allocate a port")));
|
|
5474
|
+
}
|
|
5475
|
+
});
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
async function startOpencodeSession(config2) {
|
|
5479
|
+
const { codeName, log: log2 } = config2;
|
|
5480
|
+
const existing = sessions.get(codeName);
|
|
5481
|
+
if (existing && existing.status === "running") return existing;
|
|
5482
|
+
const restartCount = existing?.restartCount ?? 0;
|
|
5483
|
+
if (existing?.status === "crashed" && existing.startedAt) {
|
|
5484
|
+
const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
|
|
5485
|
+
if (Date.now() - existing.startedAt < backoffMs) return existing;
|
|
5486
|
+
}
|
|
5487
|
+
if (config2.isolated) {
|
|
5488
|
+
log2(
|
|
5489
|
+
`[opencode-session] refusing to spawn '${codeName}': Docker isolation requested but the opencode container runtime is not implemented yet (ADR-0047). Not running unisolated on a multi-tenant host.`
|
|
5490
|
+
);
|
|
5491
|
+
const blocked = {
|
|
5492
|
+
codeName,
|
|
5493
|
+
startedAt: Date.now(),
|
|
5494
|
+
restartCount: restartCount + 1,
|
|
5495
|
+
status: "crashed",
|
|
5496
|
+
port: null,
|
|
5497
|
+
password: null,
|
|
5498
|
+
lastFailureTail: "opencode isolation not implemented (ADR-0047)"
|
|
5499
|
+
};
|
|
5500
|
+
sessions.set(codeName, blocked);
|
|
5501
|
+
return blocked;
|
|
5502
|
+
}
|
|
5503
|
+
const session = {
|
|
5504
|
+
codeName,
|
|
5505
|
+
startedAt: null,
|
|
5506
|
+
restartCount,
|
|
5507
|
+
status: "starting",
|
|
5508
|
+
port: null,
|
|
5509
|
+
password: null,
|
|
5510
|
+
lastFailureTail: existing?.lastFailureTail ?? null
|
|
5511
|
+
};
|
|
5512
|
+
sessions.set(codeName, session);
|
|
5513
|
+
try {
|
|
5514
|
+
await spawnServe(config2, session);
|
|
5515
|
+
} catch (err) {
|
|
5516
|
+
log2(`[opencode-session] failed to start '${codeName}': ${err.message}`);
|
|
5517
|
+
session.status = "crashed";
|
|
5518
|
+
session.startedAt = Date.now();
|
|
5519
|
+
session.restartCount++;
|
|
5520
|
+
}
|
|
5521
|
+
return session;
|
|
5522
|
+
}
|
|
5523
|
+
async function spawnServe(config2, session) {
|
|
5524
|
+
const { codeName, projectDir, log: log2 } = config2;
|
|
5525
|
+
if (!existsSync9(join18(projectDir, "opencode.json"))) {
|
|
5526
|
+
log2(`[opencode-session] warning: no opencode.json in ${projectDir} for '${codeName}' (provisioning may not have run)`);
|
|
5527
|
+
}
|
|
5528
|
+
const tmuxSession = opencodeTmuxSession(codeName);
|
|
5529
|
+
const port = await findFreePort();
|
|
5530
|
+
const password = randomBytes(24).toString("base64url");
|
|
5531
|
+
try {
|
|
5532
|
+
execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
5533
|
+
} catch {
|
|
5534
|
+
}
|
|
5535
|
+
mkdirSync6(join18(homedir9(), ".augmented", codeName), { recursive: true });
|
|
5536
|
+
const serveEnv = {
|
|
5537
|
+
...process.env,
|
|
5538
|
+
...stripUndefined(config2.serveEnv ?? {}),
|
|
5539
|
+
OPENCODE_SERVER_PASSWORD: password,
|
|
5540
|
+
HOME: process.env.HOME?.trim() || homedir9(),
|
|
5541
|
+
USER: process.env.USER?.trim() || userInfo().username
|
|
5542
|
+
};
|
|
5543
|
+
if (config2.runId) serveEnv["AGT_RUN_ID"] = config2.runId;
|
|
5544
|
+
if (config2.agentTimezone) serveEnv["TZ"] = config2.agentTimezone;
|
|
5545
|
+
const serveCmd = `${OPENCODE_BIN} serve --hostname 127.0.0.1 --port ${port} --print-logs`;
|
|
5546
|
+
log2(`[opencode-session] starting '${tmuxSession}' for '${codeName}' on 127.0.0.1:${port}`);
|
|
5547
|
+
const child = spawn(
|
|
5548
|
+
"tmux",
|
|
5549
|
+
["new-session", "-d", "-s", tmuxSession, "-c", projectDir, serveCmd],
|
|
5550
|
+
{ cwd: projectDir, stdio: ["ignore", "pipe", "pipe"], env: serveEnv }
|
|
5551
|
+
);
|
|
5552
|
+
child.on("close", (code) => {
|
|
5553
|
+
if (code !== 0) {
|
|
5554
|
+
log2(`[opencode-session] failed to create tmux session for '${codeName}' (exit ${code})`);
|
|
5555
|
+
session.status = "crashed";
|
|
5556
|
+
session.startedAt = Date.now();
|
|
5557
|
+
session.restartCount++;
|
|
5558
|
+
return;
|
|
5559
|
+
}
|
|
5560
|
+
log2(`[opencode-session] tmux session '${tmuxSession}' created for '${codeName}'`);
|
|
5561
|
+
setupPaneLog(tmuxSession, codeName, log2);
|
|
5562
|
+
});
|
|
5563
|
+
child.on("error", (err) => {
|
|
5564
|
+
log2(`[opencode-session] failed to start tmux for '${codeName}': ${err.message}`);
|
|
5565
|
+
session.status = "crashed";
|
|
5566
|
+
session.startedAt = Date.now();
|
|
5567
|
+
session.restartCount++;
|
|
5568
|
+
});
|
|
5569
|
+
session.port = port;
|
|
5570
|
+
session.password = password;
|
|
5571
|
+
session.startedAt = Date.now();
|
|
5572
|
+
session.status = "running";
|
|
5573
|
+
session.restartCount = 0;
|
|
5574
|
+
bridges.delete(codeName);
|
|
5575
|
+
}
|
|
5576
|
+
function setupPaneLog(tmuxSession, codeName, log2) {
|
|
5577
|
+
const logPath = opencodePaneLogPath(codeName);
|
|
5578
|
+
try {
|
|
5579
|
+
execSync(`tmux pipe-pane -t ${tmuxSession} -o 'cat >> ${logPath}'`, { stdio: "ignore" });
|
|
5580
|
+
} catch (err) {
|
|
5581
|
+
log2(`[opencode-session] could not attach pane log for '${codeName}': ${err.message}`);
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
function readOpencodePaneLogTail(codeName, lines = 40) {
|
|
5585
|
+
const logPath = opencodePaneLogPath(codeName);
|
|
5586
|
+
if (!existsSync9(logPath)) return null;
|
|
5587
|
+
try {
|
|
5588
|
+
const all = readFileSync15(logPath, "utf8").split("\n");
|
|
5589
|
+
return all.slice(-lines).join("\n");
|
|
5590
|
+
} catch {
|
|
5591
|
+
return null;
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
function isOpencodeSessionHealthy(codeName) {
|
|
5595
|
+
const tmuxSession = opencodeTmuxSession(codeName);
|
|
5596
|
+
try {
|
|
5597
|
+
execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
5598
|
+
return true;
|
|
5599
|
+
} catch {
|
|
5600
|
+
return false;
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
async function injectOpencodeMessage(codeName, msg, opts = {}) {
|
|
5604
|
+
const session = sessions.get(codeName);
|
|
5605
|
+
if (!session || session.status !== "running" || !session.port || !session.password) {
|
|
5606
|
+
return { status: "declined", reason: "server_not_running" };
|
|
5607
|
+
}
|
|
5608
|
+
const bridge = getBridge(codeName, session.port, session.password);
|
|
5609
|
+
return bridge.handleInbound(msg, { gate: opts.gate, awaitReply: opts.awaitReply });
|
|
5610
|
+
}
|
|
5611
|
+
function getBridge(codeName, port, password) {
|
|
5612
|
+
const cached = bridges.get(codeName);
|
|
5613
|
+
if (cached && cached.port === port && cached.password === password) return cached.bridge;
|
|
5614
|
+
const client2 = new HttpOpencodeClient({ baseUrl: baseUrlFor(port), password });
|
|
5615
|
+
const bridge = new OpencodeInboundBridge({ client: client2 });
|
|
5616
|
+
bridges.set(codeName, { port, password, bridge });
|
|
5617
|
+
return bridge;
|
|
5618
|
+
}
|
|
5619
|
+
function stripUndefined(env) {
|
|
5620
|
+
const out = {};
|
|
5621
|
+
for (const [k, v] of Object.entries(env)) if (v !== void 0) out[k] = v;
|
|
5622
|
+
return out;
|
|
5623
|
+
}
|
|
5624
|
+
|
|
5625
|
+
// src/lib/opencode-slack-ingest.ts
|
|
5626
|
+
import { createHash as createHash11 } from "crypto";
|
|
5627
|
+
|
|
5628
|
+
// ../../packages/core/dist/channels/slack-rich-text.js
|
|
5629
|
+
var MAX_DEPTH = 12;
|
|
5630
|
+
function isRecord(v) {
|
|
5631
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5632
|
+
}
|
|
5633
|
+
function asString(v) {
|
|
5634
|
+
return typeof v === "string" ? v : "";
|
|
5635
|
+
}
|
|
5636
|
+
function asArray(v) {
|
|
5637
|
+
return Array.isArray(v) ? v : [];
|
|
5638
|
+
}
|
|
5639
|
+
function isTableElement(el) {
|
|
5640
|
+
return isRecord(el) && asString(el.type).includes("table");
|
|
5641
|
+
}
|
|
5642
|
+
function renderEmoji(el) {
|
|
5643
|
+
const unicode = asString(el.unicode);
|
|
5644
|
+
if (unicode) {
|
|
5645
|
+
try {
|
|
5646
|
+
const cps = unicode.split("-").map((h) => parseInt(h, 16));
|
|
5647
|
+
if (cps.every((n) => Number.isFinite(n) && n >= 0 && n <= 1114111)) {
|
|
5648
|
+
return String.fromCodePoint(...cps);
|
|
5649
|
+
}
|
|
5650
|
+
} catch {
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
const name = asString(el.name);
|
|
5654
|
+
return name ? `:${name}:` : "";
|
|
5655
|
+
}
|
|
5656
|
+
function renderLeaf(el) {
|
|
5657
|
+
if (!isRecord(el))
|
|
5658
|
+
return "";
|
|
5659
|
+
switch (asString(el.type)) {
|
|
5660
|
+
case "text":
|
|
5661
|
+
return asString(el.text);
|
|
5662
|
+
case "link": {
|
|
5663
|
+
const label = asString(el.text);
|
|
5664
|
+
const url = asString(el.url);
|
|
5665
|
+
if (label && url && label !== url)
|
|
5666
|
+
return `${label} (${url})`;
|
|
5667
|
+
return label || url;
|
|
5668
|
+
}
|
|
5669
|
+
case "emoji":
|
|
5670
|
+
return renderEmoji(el);
|
|
5671
|
+
case "user":
|
|
5672
|
+
return `<@${asString(el.user_id)}>`;
|
|
5673
|
+
case "usergroup":
|
|
5674
|
+
return `<!subteam^${asString(el.usergroup_id)}>`;
|
|
5675
|
+
case "channel":
|
|
5676
|
+
return `<#${asString(el.channel_id)}>`;
|
|
5677
|
+
case "broadcast":
|
|
5678
|
+
return `@${asString(el.range) || "channel"}`;
|
|
5679
|
+
case "date":
|
|
5680
|
+
return asString(el.fallback) || asString(el.timestamp);
|
|
5681
|
+
default:
|
|
5682
|
+
if (Array.isArray(el.elements))
|
|
5683
|
+
return renderInline(el.elements);
|
|
5684
|
+
return asString(el.text);
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
function renderInline(elements) {
|
|
5688
|
+
return elements.map(renderLeaf).join("");
|
|
5689
|
+
}
|
|
5690
|
+
function deepText(node, depth) {
|
|
5691
|
+
if (depth > MAX_DEPTH)
|
|
5692
|
+
return "";
|
|
5693
|
+
if (typeof node === "string")
|
|
5694
|
+
return node;
|
|
5695
|
+
if (Array.isArray(node))
|
|
5696
|
+
return node.map((n) => deepText(n, depth + 1)).join("");
|
|
5697
|
+
if (!isRecord(node))
|
|
5698
|
+
return "";
|
|
5699
|
+
const type = asString(node.type);
|
|
5700
|
+
if (type && type !== "rich_text" && !type.startsWith("rich_text_") && !type.includes("table")) {
|
|
5701
|
+
return renderLeaf(node);
|
|
5702
|
+
}
|
|
5703
|
+
if (Array.isArray(node.elements))
|
|
5704
|
+
return node.elements.map((n) => deepText(n, depth + 1)).join("");
|
|
5705
|
+
if (Array.isArray(node.rows))
|
|
5706
|
+
return node.rows.map((n) => deepText(n, depth + 1)).join(" ");
|
|
5707
|
+
return asString(node.text);
|
|
5708
|
+
}
|
|
5709
|
+
function renderCell(cell) {
|
|
5710
|
+
if (!isRecord(cell))
|
|
5711
|
+
return deepText(cell, 0).replace(/\s*\n\s*/g, " ").trim();
|
|
5712
|
+
const inner = Array.isArray(cell.elements) ? renderSection(cell, 0) : deepText(cell, 0);
|
|
5713
|
+
return inner.replace(/\s*\n\s*/g, " ").trim();
|
|
5714
|
+
}
|
|
5715
|
+
function renderTable(el) {
|
|
5716
|
+
const rawRows = asArray(el.elements).length ? asArray(el.elements) : asArray(el.rows);
|
|
5717
|
+
const rows = [];
|
|
5718
|
+
for (const row of rawRows) {
|
|
5719
|
+
if (!isRecord(row) && !Array.isArray(row))
|
|
5720
|
+
continue;
|
|
5721
|
+
const rawCells = Array.isArray(row) ? row : asArray(row.elements).length ? asArray(row.elements) : asArray(row.cells);
|
|
5722
|
+
if (rawCells.length === 0)
|
|
5723
|
+
continue;
|
|
5724
|
+
rows.push(rawCells.map(renderCell));
|
|
5725
|
+
}
|
|
5726
|
+
if (rows.length === 0) {
|
|
5727
|
+
const fallback = deepText(el, 0).replace(/[ \t]+\n/g, "\n").trim();
|
|
5728
|
+
return fallback;
|
|
5729
|
+
}
|
|
5730
|
+
const colCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
|
|
5731
|
+
const pad = (r) => {
|
|
5732
|
+
const cells = r.slice();
|
|
5733
|
+
while (cells.length < colCount)
|
|
5734
|
+
cells.push("");
|
|
5735
|
+
return cells.map((c) => c.replace(/\|/g, "\\|"));
|
|
5736
|
+
};
|
|
5737
|
+
const lines = [];
|
|
5738
|
+
const header = rows[0] ?? [];
|
|
5739
|
+
const body = rows.slice(1);
|
|
5740
|
+
lines.push(`| ${pad(header).join(" | ")} |`);
|
|
5741
|
+
lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
|
|
5742
|
+
for (const r of body)
|
|
5743
|
+
lines.push(`| ${pad(r).join(" | ")} |`);
|
|
5744
|
+
return lines.join("\n");
|
|
5745
|
+
}
|
|
5746
|
+
function renderSection(section, depth) {
|
|
5747
|
+
if (depth > MAX_DEPTH || !isRecord(section))
|
|
5748
|
+
return "";
|
|
5749
|
+
const type = asString(section.type);
|
|
5750
|
+
if (isTableElement(section))
|
|
5751
|
+
return renderTable(section);
|
|
5752
|
+
switch (type) {
|
|
5753
|
+
case "rich_text_section":
|
|
5754
|
+
return renderInline(asArray(section.elements));
|
|
5755
|
+
case "rich_text_preformatted": {
|
|
5756
|
+
const code = renderInline(asArray(section.elements));
|
|
5757
|
+
return code ? `\`\`\`
|
|
5758
|
+
${code}
|
|
5759
|
+
\`\`\`` : "";
|
|
5760
|
+
}
|
|
5761
|
+
case "rich_text_quote": {
|
|
5762
|
+
const quoted = renderInline(asArray(section.elements));
|
|
5763
|
+
return quoted ? quoted.split("\n").map((l) => `> ${l}`).join("\n") : "";
|
|
5764
|
+
}
|
|
5765
|
+
case "rich_text_list": {
|
|
5766
|
+
const ordered = asString(section.style) === "ordered";
|
|
5767
|
+
const items = asArray(section.elements).map((item, i) => {
|
|
5768
|
+
const body = renderSection(item, depth + 1) || renderInline(asArray(item?.elements));
|
|
5769
|
+
const marker = ordered ? `${i + 1}.` : "-";
|
|
5770
|
+
return `${marker} ${body}`.trimEnd();
|
|
5771
|
+
});
|
|
5772
|
+
return items.join("\n");
|
|
5773
|
+
}
|
|
5774
|
+
default:
|
|
5775
|
+
if (Array.isArray(section.elements))
|
|
5776
|
+
return renderInline(asArray(section.elements));
|
|
5777
|
+
return deepText(section, depth + 1);
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
function renderSlackBlocks(blocks) {
|
|
5781
|
+
let hadTable = false;
|
|
5782
|
+
const out = [];
|
|
5783
|
+
try {
|
|
5784
|
+
for (const block of asArray(blocks)) {
|
|
5785
|
+
if (!isRecord(block))
|
|
5786
|
+
continue;
|
|
5787
|
+
if (isTableElement(block)) {
|
|
5788
|
+
hadTable = true;
|
|
5789
|
+
const rendered = renderTable(block);
|
|
5790
|
+
if (rendered.trim().length > 0)
|
|
5791
|
+
out.push(rendered);
|
|
5792
|
+
continue;
|
|
5793
|
+
}
|
|
5794
|
+
if (asString(block.type) !== "rich_text")
|
|
5795
|
+
continue;
|
|
5796
|
+
for (const section of asArray(block.elements)) {
|
|
5797
|
+
if (isTableElement(section))
|
|
5798
|
+
hadTable = true;
|
|
5799
|
+
const rendered = renderSection(section, 0);
|
|
5800
|
+
if (rendered.trim().length > 0)
|
|
5801
|
+
out.push(rendered);
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
} catch {
|
|
5805
|
+
}
|
|
5806
|
+
const text = out.join("\n\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
5807
|
+
return { text, hadTable };
|
|
5808
|
+
}
|
|
5809
|
+
|
|
5810
|
+
// ../../packages/core/dist/channels/governance/slack-forwarded.js
|
|
5811
|
+
function asTrimmed(v) {
|
|
5812
|
+
return typeof v === "string" ? v.trim() : "";
|
|
5813
|
+
}
|
|
5814
|
+
function extractForwardedContent(attachments) {
|
|
5815
|
+
if (!Array.isArray(attachments))
|
|
5816
|
+
return { text: "", files: [] };
|
|
5817
|
+
const parts = [];
|
|
5818
|
+
const files = [];
|
|
5819
|
+
for (const raw of attachments) {
|
|
5820
|
+
if (!raw || typeof raw !== "object")
|
|
5821
|
+
continue;
|
|
5822
|
+
const att = raw;
|
|
5823
|
+
const textBody = asTrimmed(att.text);
|
|
5824
|
+
let body = textBody || asTrimmed(att.fallback);
|
|
5825
|
+
const blockSources = [];
|
|
5826
|
+
if (Array.isArray(att.blocks))
|
|
5827
|
+
blockSources.push(att.blocks);
|
|
5828
|
+
if (Array.isArray(att.message_blocks)) {
|
|
5829
|
+
for (const mb of att.message_blocks)
|
|
5830
|
+
blockSources.push(mb?.message?.blocks);
|
|
5831
|
+
}
|
|
5832
|
+
for (const source of blockSources) {
|
|
5833
|
+
const rendered = renderSlackBlocks(source);
|
|
5834
|
+
const renderedText = rendered.text.trim();
|
|
5835
|
+
if (renderedText && (!textBody || rendered.hadTable)) {
|
|
5836
|
+
body = renderedText;
|
|
5837
|
+
break;
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
const title = asTrimmed(att.title);
|
|
5841
|
+
const link = asTrimmed(att.title_link) || asTrimmed(att.from_url);
|
|
5842
|
+
const imageUrl = asTrimmed(att.image_url) || asTrimmed(att.thumb_url);
|
|
5843
|
+
const lines = [];
|
|
5844
|
+
if (body)
|
|
5845
|
+
lines.push(body);
|
|
5846
|
+
if (title && title !== body)
|
|
5847
|
+
lines.push(title);
|
|
5848
|
+
if (link)
|
|
5849
|
+
lines.push(link);
|
|
5850
|
+
if (imageUrl)
|
|
5851
|
+
lines.push(`[image] ${imageUrl}`);
|
|
5852
|
+
if (lines.length)
|
|
5853
|
+
parts.push(lines.join("\n"));
|
|
5854
|
+
if (Array.isArray(att.files))
|
|
5855
|
+
files.push(...att.files);
|
|
5856
|
+
}
|
|
5857
|
+
return { text: parts.join("\n\n"), files };
|
|
5858
|
+
}
|
|
5859
|
+
function hasForwardedContent(attachments) {
|
|
5860
|
+
const { text, files } = extractForwardedContent(attachments);
|
|
5861
|
+
return text.length > 0 || files.length > 0;
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
// ../../packages/core/dist/channels/governance/slack-inbound-filter.js
|
|
5865
|
+
var ALLOWED_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
|
|
5866
|
+
void 0,
|
|
5867
|
+
"file_share",
|
|
5868
|
+
"thread_broadcast"
|
|
5869
|
+
]);
|
|
5870
|
+
function decideSlackMessageForward(evt) {
|
|
5871
|
+
if (evt.type !== "message") {
|
|
5872
|
+
return { forward: true };
|
|
5873
|
+
}
|
|
5874
|
+
if (!ALLOWED_MESSAGE_SUBTYPES.has(evt.subtype)) {
|
|
5875
|
+
return { forward: false, reason: "subtype" };
|
|
5876
|
+
}
|
|
5877
|
+
if (!evt.user) {
|
|
5878
|
+
return { forward: false, reason: "no_user" };
|
|
5879
|
+
}
|
|
5880
|
+
const hasText = typeof evt.text === "string" && evt.text.trim().length > 0;
|
|
5881
|
+
const hasFiles = Array.isArray(evt.files) && evt.files.length > 0;
|
|
5882
|
+
const hasBlocks = Array.isArray(evt.blocks) && evt.blocks.length > 0;
|
|
5883
|
+
const hasAttachments = hasForwardedContent(evt.attachments);
|
|
5884
|
+
if (!hasText && !hasFiles && !hasBlocks && !hasAttachments) {
|
|
5885
|
+
return { forward: false, reason: "empty" };
|
|
5886
|
+
}
|
|
5887
|
+
return { forward: true };
|
|
5888
|
+
}
|
|
5889
|
+
function isAppMentionEcho(evt, botUserId) {
|
|
5890
|
+
if (evt.type !== "message")
|
|
5891
|
+
return false;
|
|
5892
|
+
if (!botUserId)
|
|
5893
|
+
return false;
|
|
5894
|
+
if (evt.channel?.startsWith("D"))
|
|
5895
|
+
return false;
|
|
5896
|
+
return typeof evt.text === "string" && evt.text.includes(`<@${botUserId}>`);
|
|
5897
|
+
}
|
|
5898
|
+
function extractAugmentedSlackLabel(evt) {
|
|
5899
|
+
if (evt.metadata?.event_type !== "augmented_agent_message")
|
|
5900
|
+
return null;
|
|
5901
|
+
return {
|
|
5902
|
+
teamId: evt.metadata.event_payload?.["augmented_team_id"],
|
|
5903
|
+
agentId: evt.metadata.event_payload?.["augmented_agent_id"]
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5906
|
+
function decideModeForward(evt, policy) {
|
|
5907
|
+
if (policy.mode === "all")
|
|
5908
|
+
return { forward: true };
|
|
5909
|
+
if (policy.mode === "manager_only") {
|
|
5910
|
+
if (policy.principalSlackUserId && evt.user === policy.principalSlackUserId) {
|
|
5911
|
+
return { forward: true };
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
if (policy.mode === "team_only") {
|
|
5915
|
+
if (policy.teamPrincipalSlackUserIds && policy.teamPrincipalSlackUserIds.length > 0 && typeof evt.user === "string" && policy.teamPrincipalSlackUserIds.includes(evt.user)) {
|
|
5916
|
+
return { forward: true };
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
const label = extractAugmentedSlackLabel(evt);
|
|
5920
|
+
if (!label)
|
|
5921
|
+
return { forward: false, reason: "sender_policy" };
|
|
5922
|
+
if (policy.mode === "team_agents_only" || policy.mode === "manager_only" || policy.mode === "team_only") {
|
|
5923
|
+
if (!policy.teamId || label.teamId !== policy.teamId) {
|
|
5924
|
+
return { forward: false, reason: "sender_policy" };
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
return { forward: true };
|
|
5928
|
+
}
|
|
5929
|
+
function decideSenderPolicyForward(evt, policy) {
|
|
5930
|
+
if (policy.internalOnly) {
|
|
5931
|
+
if (!policy.homeTeamId)
|
|
5932
|
+
return { forward: false, reason: "sender_policy" };
|
|
5933
|
+
if (evt.team !== policy.homeTeamId)
|
|
5934
|
+
return { forward: false, reason: "sender_policy" };
|
|
5935
|
+
}
|
|
5936
|
+
return decideModeForward(evt, policy);
|
|
5937
|
+
}
|
|
5938
|
+
|
|
5939
|
+
// ../../packages/core/dist/channels/governance/slack-peer-classifier.js
|
|
5940
|
+
var CODE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
5941
|
+
function classifyPeerMessage(msg, cfg, self) {
|
|
5942
|
+
if (!msg.bot_id)
|
|
5943
|
+
return { kind: "human" };
|
|
5944
|
+
if (self.bot_user_id !== null && msg.user === self.bot_user_id) {
|
|
5945
|
+
return { kind: "self" };
|
|
5946
|
+
}
|
|
5947
|
+
if (cfg.peer_disabled_mode === "all") {
|
|
5948
|
+
return { kind: "drop", reason: "peer_disabled_all" };
|
|
5949
|
+
}
|
|
5950
|
+
if (cfg.peer_agent_mode === "off") {
|
|
5951
|
+
return { kind: "drop", reason: "mode_off" };
|
|
5952
|
+
}
|
|
5953
|
+
if (cfg.peer_group_ids.length === 0) {
|
|
5954
|
+
if (msg.channel_type !== "im") {
|
|
5955
|
+
return { kind: "drop", reason: "chat_not_allowed" };
|
|
5956
|
+
}
|
|
5957
|
+
} else if (!cfg.peer_group_ids.includes(msg.channel)) {
|
|
5958
|
+
return { kind: "drop", reason: "chat_not_allowed" };
|
|
5959
|
+
}
|
|
5960
|
+
const peer = cfg.peers.find((p) => p.bot_user_id === msg.user);
|
|
5961
|
+
if (!peer) {
|
|
5962
|
+
return { kind: "drop", reason: "unknown_peer" };
|
|
5963
|
+
}
|
|
5964
|
+
if (peer.gate_path === null) {
|
|
5965
|
+
return { kind: "drop", reason: "cross_team_grant_missing" };
|
|
5966
|
+
}
|
|
5967
|
+
if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
|
|
5968
|
+
return { kind: "drop", reason: "peer_disabled_cross_team" };
|
|
5969
|
+
}
|
|
5970
|
+
if (self.bot_user_id === null) {
|
|
5971
|
+
return { kind: "drop", reason: "self_resolution_pending" };
|
|
5972
|
+
}
|
|
5973
|
+
const addressing = classifyAddressing(msg, self.bot_user_id);
|
|
5974
|
+
if (!addressing.addressed) {
|
|
5975
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
5976
|
+
}
|
|
5977
|
+
if (addressing.viaReplyOnly) {
|
|
5978
|
+
const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(msg.channel);
|
|
5979
|
+
const respondMode = cfg.peer_agent_mode === "respond";
|
|
5980
|
+
if (!(inAllowedGroup && respondMode)) {
|
|
5981
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
return { kind: "peer-ingress", peer };
|
|
5985
|
+
}
|
|
5986
|
+
function classifyAddressing(msg, ourBotUserId) {
|
|
5987
|
+
const text = msg.text ?? "";
|
|
5988
|
+
const mentionToken = `<@${ourBotUserId}>`;
|
|
5989
|
+
const viaMention = text.includes(mentionToken);
|
|
5990
|
+
const viaReply = !!msg.thread_ts && msg.thread_ts !== void 0 && msg.parent_user_id === ourBotUserId;
|
|
5991
|
+
if (viaMention) {
|
|
5992
|
+
return { addressed: true, viaReplyOnly: false };
|
|
5993
|
+
}
|
|
5994
|
+
if (viaReply) {
|
|
5995
|
+
return { addressed: true, viaReplyOnly: true };
|
|
5996
|
+
}
|
|
5997
|
+
return { addressed: false, viaReplyOnly: false };
|
|
5998
|
+
}
|
|
5999
|
+
function parsePeersEnv(raw, gateRaw) {
|
|
6000
|
+
if (!raw || raw.trim().length === 0)
|
|
6001
|
+
return [];
|
|
6002
|
+
let parsed;
|
|
6003
|
+
try {
|
|
6004
|
+
parsed = JSON.parse(raw);
|
|
6005
|
+
} catch {
|
|
6006
|
+
return [];
|
|
6007
|
+
}
|
|
6008
|
+
if (!Array.isArray(parsed))
|
|
6009
|
+
return [];
|
|
6010
|
+
const gateMap = /* @__PURE__ */ new Map();
|
|
6011
|
+
let gateConfigInvalid = false;
|
|
6012
|
+
if (gateRaw && gateRaw.trim().length > 0) {
|
|
6013
|
+
try {
|
|
6014
|
+
const gateParsed = JSON.parse(gateRaw);
|
|
6015
|
+
if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
|
|
6016
|
+
gateConfigInvalid = true;
|
|
6017
|
+
} else {
|
|
6018
|
+
for (const [k, v] of Object.entries(gateParsed)) {
|
|
6019
|
+
if (v === null) {
|
|
6020
|
+
gateMap.set(k, null);
|
|
6021
|
+
} else if (typeof v === "string") {
|
|
6022
|
+
const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
|
|
6023
|
+
if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
|
|
6024
|
+
gateMap.set(k, v);
|
|
6025
|
+
} else {
|
|
6026
|
+
gateConfigInvalid = true;
|
|
6027
|
+
break;
|
|
6028
|
+
}
|
|
6029
|
+
} else {
|
|
6030
|
+
gateConfigInvalid = true;
|
|
6031
|
+
break;
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
} catch {
|
|
6036
|
+
gateConfigInvalid = true;
|
|
6037
|
+
}
|
|
6038
|
+
if (gateConfigInvalid) {
|
|
6039
|
+
console.error("[slack-peer-classifier] SLACK_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)");
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
const out = [];
|
|
6043
|
+
for (const entry of parsed) {
|
|
6044
|
+
if (entry && typeof entry === "object" && typeof entry.code_name === "string" && CODE_NAME_RE.test(entry.code_name) && typeof entry.agent_id === "string" && typeof entry.bot_user_id === "string" && entry.bot_user_id.length > 0) {
|
|
6045
|
+
const e = entry;
|
|
6046
|
+
const peer = {
|
|
6047
|
+
code_name: e.code_name,
|
|
6048
|
+
bot_user_id: e.bot_user_id,
|
|
6049
|
+
agent_id: e.agent_id
|
|
6050
|
+
};
|
|
6051
|
+
if (gateConfigInvalid) {
|
|
6052
|
+
peer.gate_path = null;
|
|
6053
|
+
} else if (gateMap.has(e.bot_user_id)) {
|
|
6054
|
+
peer.gate_path = gateMap.get(e.bot_user_id) ?? null;
|
|
6055
|
+
}
|
|
6056
|
+
out.push(peer);
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
return out;
|
|
6060
|
+
}
|
|
6061
|
+
function parsePeerGroupIdsEnv(raw) {
|
|
6062
|
+
if (!raw)
|
|
6063
|
+
return [];
|
|
6064
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6065
|
+
}
|
|
6066
|
+
function parsePeerAgentModeEnv(raw) {
|
|
6067
|
+
if (raw === "listen" || raw === "respond")
|
|
6068
|
+
return raw;
|
|
6069
|
+
return "off";
|
|
6070
|
+
}
|
|
6071
|
+
|
|
6072
|
+
// ../../packages/core/dist/channels/governance/sender-policy-decline.js
|
|
6073
|
+
function classifySlackPolicyBlock(evt, policy) {
|
|
6074
|
+
if (policy.internalOnly && (!policy.homeTeamId || evt.team !== policy.homeTeamId)) {
|
|
6075
|
+
return "internal_only";
|
|
6076
|
+
}
|
|
6077
|
+
switch (policy.mode) {
|
|
6078
|
+
case "manager_only":
|
|
6079
|
+
return "mode_manager_only";
|
|
6080
|
+
case "team_only":
|
|
6081
|
+
return "mode_team_only";
|
|
6082
|
+
case "team_agents_only":
|
|
6083
|
+
return "mode_team_agents_only";
|
|
6084
|
+
case "agents_only":
|
|
6085
|
+
return "mode_agents_only";
|
|
6086
|
+
case "all":
|
|
6087
|
+
return "internal_only";
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
6090
|
+
function politeDeclineCopy(reason) {
|
|
6091
|
+
switch (reason) {
|
|
6092
|
+
case "internal_only":
|
|
6093
|
+
return "Sorry, I can only respond to people inside our organisation. This conversation appears to be from outside.";
|
|
6094
|
+
case "mode_manager_only":
|
|
6095
|
+
return "Sorry, I only respond to my manager in this channel.";
|
|
6096
|
+
case "mode_team_only":
|
|
6097
|
+
return "Sorry, this agent only replies to its team members.";
|
|
6098
|
+
case "mode_team_agents_only":
|
|
6099
|
+
return "Sorry, I only respond to agents on my team in this channel.";
|
|
6100
|
+
case "mode_agents_only":
|
|
6101
|
+
return "Sorry, I only respond to other agents in this channel.";
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6105
|
+
// ../../packages/core/dist/channels/governance/inbound-access.js
|
|
6106
|
+
function decideInboundAccess(input) {
|
|
6107
|
+
if (!input.content.forward) {
|
|
6108
|
+
return { kind: "drop", reason: `content:${input.content.reason}` };
|
|
6109
|
+
}
|
|
6110
|
+
if (input.isSelf) {
|
|
6111
|
+
return { kind: "drop", reason: "self" };
|
|
6112
|
+
}
|
|
6113
|
+
if (input.orgBoundary && !input.orgBoundary.forward) {
|
|
6114
|
+
return { kind: "drop", reason: "org_boundary" };
|
|
6115
|
+
}
|
|
6116
|
+
if (input.senderPolicy && !input.senderPolicy.forward) {
|
|
6117
|
+
const reason = input.senderPolicy.reason;
|
|
6118
|
+
const declineWorthy = input.sameOrg && reason !== "internal_only";
|
|
6119
|
+
return {
|
|
6120
|
+
kind: "drop",
|
|
6121
|
+
reason: `sender_policy:${reason}`,
|
|
6122
|
+
decline: declineWorthy ? politeDeclineCopy(reason) : void 0
|
|
6123
|
+
};
|
|
6124
|
+
}
|
|
6125
|
+
if (input.command && !input.isBot) {
|
|
6126
|
+
return {
|
|
6127
|
+
kind: "command",
|
|
6128
|
+
command: input.command.command,
|
|
6129
|
+
authorized: input.command.authorized
|
|
6130
|
+
};
|
|
6131
|
+
}
|
|
6132
|
+
if (input.isBot) {
|
|
6133
|
+
const peer = input.peer;
|
|
6134
|
+
if (!peer || peer.kind === "self") {
|
|
6135
|
+
return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
|
|
6136
|
+
}
|
|
6137
|
+
if (peer.kind === "drop") {
|
|
6138
|
+
return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
return { kind: "admit" };
|
|
6142
|
+
}
|
|
6143
|
+
|
|
6144
|
+
// ../../packages/core/dist/channels/governance/slack-sender-gate.js
|
|
6145
|
+
function evaluateSlackInboundGate(evt, deps) {
|
|
6146
|
+
const isBot = Boolean(evt.bot_id);
|
|
6147
|
+
const content = decideSlackMessageForward(evt);
|
|
6148
|
+
const spDecision = decideSenderPolicyForward(evt, deps.senderPolicy);
|
|
6149
|
+
const senderPolicy = spDecision.forward ? { forward: true } : { forward: false, reason: classifySlackPolicyBlock(evt, deps.senderPolicy) };
|
|
6150
|
+
const peer = isBot ? (() => {
|
|
6151
|
+
const c = classifyPeerMessage({
|
|
6152
|
+
text: evt.text,
|
|
6153
|
+
channel: evt.channel ?? "",
|
|
6154
|
+
channel_type: evt.channel_type,
|
|
6155
|
+
user: evt.user,
|
|
6156
|
+
bot_id: evt.bot_id,
|
|
6157
|
+
thread_ts: evt.thread_ts,
|
|
6158
|
+
parent_user_id: evt.parent_user_id
|
|
6159
|
+
}, deps.peerConfig, { bot_user_id: deps.botUserId ?? null });
|
|
6160
|
+
return { kind: c.kind, reason: c.kind === "drop" ? c.reason : void 0 };
|
|
6161
|
+
})() : void 0;
|
|
6162
|
+
const decision = decideInboundAccess({
|
|
6163
|
+
content,
|
|
6164
|
+
isSelf: Boolean(evt.user && deps.botUserId && evt.user === deps.botUserId),
|
|
6165
|
+
isBot,
|
|
6166
|
+
peer,
|
|
6167
|
+
senderPolicy,
|
|
6168
|
+
// Injection gate: no command dispatch and no same-org decline copy needed.
|
|
6169
|
+
sameOrg: false
|
|
6170
|
+
});
|
|
6171
|
+
if (decision.kind === "drop")
|
|
6172
|
+
return { admit: false, reason: decision.reason };
|
|
6173
|
+
return { admit: true };
|
|
6174
|
+
}
|
|
6175
|
+
|
|
6176
|
+
// ../../packages/core/dist/channels/governance/slack-inbound-config.js
|
|
6177
|
+
function buildSlackSenderPolicyFromEnv(env, ctx) {
|
|
6178
|
+
const raw = (env["SLACK_SENDER_POLICY"] ?? "all").trim().toLowerCase();
|
|
6179
|
+
const internalOnly = env["SLACK_INTERNAL_ONLY"] === "true";
|
|
6180
|
+
const homeTeamId = env["SLACK_HOME_TEAM_ID"];
|
|
6181
|
+
if (internalOnly && !homeTeamId) {
|
|
6182
|
+
throw new Error("SLACK_INTERNAL_ONLY=true requires SLACK_HOME_TEAM_ID. The bot install must have a known team_id (run auth.test against the workspace and persist the result).");
|
|
6183
|
+
}
|
|
6184
|
+
const internalOnlyFields = internalOnly ? { internalOnly: true, homeTeamId } : {};
|
|
6185
|
+
const teamId = ctx.agentTeamId;
|
|
6186
|
+
if (raw === "all")
|
|
6187
|
+
return { mode: "all", ...internalOnlyFields };
|
|
6188
|
+
if (raw === "agents_only")
|
|
6189
|
+
return { mode: "agents_only", ...internalOnlyFields };
|
|
6190
|
+
if (raw === "team_agents_only") {
|
|
6191
|
+
if (!teamId) {
|
|
6192
|
+
throw new Error("SLACK_SENDER_POLICY=team_agents_only requires AGT_TEAM_ID");
|
|
6193
|
+
}
|
|
6194
|
+
return { mode: "team_agents_only", teamId, ...internalOnlyFields };
|
|
6195
|
+
}
|
|
6196
|
+
if (raw === "team_only") {
|
|
6197
|
+
if (!teamId) {
|
|
6198
|
+
throw new Error("SLACK_SENDER_POLICY=team_only requires AGT_TEAM_ID");
|
|
6199
|
+
}
|
|
6200
|
+
const teamPrincipalSlackUserIds = (env["SLACK_SENDER_POLICY_TEAM_PRINCIPAL_IDS"] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6201
|
+
return { mode: "team_only", teamId, teamPrincipalSlackUserIds, ...internalOnlyFields };
|
|
6202
|
+
}
|
|
6203
|
+
if (raw === "manager_only") {
|
|
6204
|
+
const principalSlackUserId = env["SLACK_SENDER_POLICY_PRINCIPAL_ID"];
|
|
6205
|
+
if (!teamId) {
|
|
6206
|
+
throw new Error("SLACK_SENDER_POLICY=manager_only requires AGT_TEAM_ID");
|
|
6207
|
+
}
|
|
6208
|
+
if (!principalSlackUserId) {
|
|
6209
|
+
throw new Error("SLACK_SENDER_POLICY=manager_only requires SLACK_SENDER_POLICY_PRINCIPAL_ID. Set agent.reports_to_person with a slack_user_id in contact_preferences and re-run /host/refresh.");
|
|
6210
|
+
}
|
|
6211
|
+
return { mode: "manager_only", teamId, principalSlackUserId, ...internalOnlyFields };
|
|
6212
|
+
}
|
|
6213
|
+
throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(env["SLACK_SENDER_POLICY"])}`);
|
|
6214
|
+
}
|
|
6215
|
+
function parseSlackPeerDisabledModeEnv(env, onWarn) {
|
|
6216
|
+
const raw = (env["PEER_DISABLED"] ?? "").trim().toLowerCase();
|
|
6217
|
+
if (raw === "" || raw === "off")
|
|
6218
|
+
return "off";
|
|
6219
|
+
if (raw === "cross_team_only" || raw === "all")
|
|
6220
|
+
return raw;
|
|
6221
|
+
onWarn?.(`invalid PEER_DISABLED=${JSON.stringify(env["PEER_DISABLED"])} - defaulting to 'all' (fail-closed)`);
|
|
6222
|
+
return "all";
|
|
6223
|
+
}
|
|
6224
|
+
function buildSlackPeerClassifierConfigFromEnv(env, opts) {
|
|
6225
|
+
return {
|
|
6226
|
+
peer_agent_mode: parsePeerAgentModeEnv(env["SLACK_PEER_AGENT_MODE"]),
|
|
6227
|
+
peer_group_ids: parsePeerGroupIdsEnv(env["SLACK_PEER_GROUP_IDS"]),
|
|
6228
|
+
peers: parsePeersEnv(env["SLACK_PEERS"], env["SLACK_PEERS_GATE"]),
|
|
6229
|
+
peer_disabled_mode: parseSlackPeerDisabledModeEnv(env, opts?.onWarn)
|
|
6230
|
+
};
|
|
6231
|
+
}
|
|
6232
|
+
|
|
6233
|
+
// src/lib/slack-ingest.ts
|
|
6234
|
+
var SLACK_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
6235
|
+
"invalid_auth",
|
|
6236
|
+
"token_revoked",
|
|
6237
|
+
"token_expired",
|
|
6238
|
+
"account_inactive",
|
|
6239
|
+
"not_authed"
|
|
6240
|
+
]);
|
|
6241
|
+
async function resolveSlackBotUserId(botToken, fetchImpl) {
|
|
6242
|
+
const doFetch = fetchImpl ?? globalThis.fetch;
|
|
6243
|
+
try {
|
|
6244
|
+
const res = await doFetch(`${SLACK_API}/auth.test`, {
|
|
6245
|
+
method: "POST",
|
|
6246
|
+
headers: { Authorization: `Bearer ${botToken}` },
|
|
6247
|
+
signal: AbortSignal.timeout(1e4)
|
|
6248
|
+
});
|
|
6249
|
+
const data = await res.json();
|
|
6250
|
+
if (data.ok) return { id: data.user_id, authFailed: false };
|
|
6251
|
+
return { id: void 0, authFailed: !!data.error && SLACK_AUTH_FAILURE_CODES.has(data.error) };
|
|
6252
|
+
} catch {
|
|
6253
|
+
return { id: void 0, authFailed: false };
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
function resolveSlackIngestParamsFromEnv(env, ctx) {
|
|
6257
|
+
const appToken = env["SLACK_APP_TOKEN"];
|
|
6258
|
+
const botToken = env["SLACK_BOT_TOKEN"];
|
|
6259
|
+
if (!appToken || !botToken) return null;
|
|
6260
|
+
return {
|
|
6261
|
+
appToken,
|
|
6262
|
+
botToken,
|
|
6263
|
+
senderPolicy: buildSlackSenderPolicyFromEnv(env, ctx),
|
|
6264
|
+
peerConfig: buildSlackPeerClassifierConfigFromEnv(env)
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
var BoundedSet = class {
|
|
6268
|
+
max;
|
|
6269
|
+
set = /* @__PURE__ */ new Set();
|
|
6270
|
+
constructor(max = 500) {
|
|
6271
|
+
this.max = max;
|
|
6272
|
+
}
|
|
6273
|
+
has(k) {
|
|
6274
|
+
return this.set.has(k);
|
|
6275
|
+
}
|
|
6276
|
+
add(k) {
|
|
6277
|
+
this.set.add(k);
|
|
6278
|
+
if (this.set.size > this.max) {
|
|
6279
|
+
const oldest = this.set.values().next().value;
|
|
6280
|
+
if (oldest !== void 0) this.set.delete(oldest);
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
};
|
|
6284
|
+
async function dispatchSlackInbound(event, ctx) {
|
|
6285
|
+
if (isAppMentionEcho(event, ctx.botUserId ?? null)) {
|
|
6286
|
+
return "dropped_echo";
|
|
6287
|
+
}
|
|
6288
|
+
const dedupKey = event.channel && event.ts ? `${event.channel}:${event.ts}` : null;
|
|
6289
|
+
if (dedupKey) {
|
|
6290
|
+
if (ctx.seen.has(dedupKey)) return "dropped_duplicate";
|
|
6291
|
+
ctx.seen.add(dedupKey);
|
|
6292
|
+
}
|
|
6293
|
+
const decision = evaluateSlackInboundGate(event, {
|
|
6294
|
+
senderPolicy: ctx.senderPolicy,
|
|
6295
|
+
peerConfig: ctx.peerConfig,
|
|
6296
|
+
botUserId: ctx.botUserId
|
|
6297
|
+
});
|
|
6298
|
+
if (!decision.admit) {
|
|
6299
|
+
ctx.log(`[slack-ingest:${ctx.codeName}] dropped ${redact(event.channel)} reason=${decision.reason}`);
|
|
6300
|
+
return "dropped_gate";
|
|
6301
|
+
}
|
|
6302
|
+
const conversationKey = `${event.channel ?? "nochannel"}:${event.thread_ts ?? event.ts ?? ""}`;
|
|
6303
|
+
const meta = {
|
|
6304
|
+
source: "slack",
|
|
6305
|
+
lane: "conversational",
|
|
6306
|
+
requires_reply: "true"
|
|
6307
|
+
};
|
|
6308
|
+
if (event.thread_ts) meta["thread_ts"] = event.thread_ts;
|
|
6309
|
+
else if (event.ts) meta["thread_ts"] = event.ts;
|
|
6310
|
+
const result = await ctx.inject({
|
|
6311
|
+
channelId: "slack",
|
|
6312
|
+
conversationKey,
|
|
6313
|
+
senderId: event.user ?? "unknown",
|
|
6314
|
+
text: event.text ?? "",
|
|
6315
|
+
meta
|
|
6316
|
+
});
|
|
6317
|
+
if (result.status === "declined") {
|
|
6318
|
+
ctx.log(`[slack-ingest:${ctx.codeName}] inject declined reason=${result.reason}`);
|
|
6319
|
+
return "declined";
|
|
6320
|
+
}
|
|
6321
|
+
if (result.status === "replied" && result.reply && event.channel) {
|
|
6322
|
+
await ctx.sendReply({
|
|
6323
|
+
channel: event.channel,
|
|
6324
|
+
text: result.reply,
|
|
6325
|
+
threadTs: event.thread_ts ?? event.ts
|
|
6326
|
+
});
|
|
6327
|
+
return "admitted_replied";
|
|
6328
|
+
}
|
|
6329
|
+
return "admitted_no_reply";
|
|
6330
|
+
}
|
|
6331
|
+
function redact(id) {
|
|
6332
|
+
if (!id) return "?";
|
|
6333
|
+
return `${id.slice(0, 1)}***`;
|
|
6334
|
+
}
|
|
6335
|
+
var SLACK_API = "https://slack.com/api";
|
|
6336
|
+
function defaultSendReply(botToken, fetchImpl, log2) {
|
|
6337
|
+
return async ({ channel, text, threadTs }) => {
|
|
6338
|
+
try {
|
|
6339
|
+
const res = await fetchImpl(`${SLACK_API}/chat.postMessage`, {
|
|
6340
|
+
method: "POST",
|
|
6341
|
+
headers: {
|
|
6342
|
+
Authorization: `Bearer ${botToken}`,
|
|
6343
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
6344
|
+
},
|
|
6345
|
+
body: JSON.stringify({ channel, text, ...threadTs ? { thread_ts: threadTs } : {} })
|
|
6346
|
+
});
|
|
6347
|
+
const data = await res.json();
|
|
6348
|
+
if (!data.ok) log2(`[slack-ingest] chat.postMessage failed: ${data.error}`);
|
|
6349
|
+
} catch (err) {
|
|
6350
|
+
log2(`[slack-ingest] chat.postMessage threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
6351
|
+
}
|
|
6352
|
+
};
|
|
6353
|
+
}
|
|
6354
|
+
function startSlackIngest(config2) {
|
|
6355
|
+
const fetchImpl = config2.fetchImpl ?? globalThis.fetch;
|
|
6356
|
+
const socketFactory = config2.socketFactory ?? ((url) => {
|
|
6357
|
+
const Ctor = globalThis.WebSocket;
|
|
6358
|
+
if (!Ctor) throw new Error("global WebSocket is unavailable (Node 22+ required for opencode Slack ingest)");
|
|
6359
|
+
return new Ctor(url);
|
|
6360
|
+
});
|
|
6361
|
+
const sendReply = config2.sendReply ?? defaultSendReply(config2.botToken, fetchImpl, config2.log);
|
|
6362
|
+
const seen = new BoundedSet();
|
|
6363
|
+
let stopped = false;
|
|
6364
|
+
let ws = null;
|
|
6365
|
+
const ctx = {
|
|
6366
|
+
codeName: config2.codeName,
|
|
6367
|
+
botUserId: config2.botUserId,
|
|
6368
|
+
senderPolicy: config2.senderPolicy,
|
|
6369
|
+
peerConfig: config2.peerConfig,
|
|
6370
|
+
inject: config2.inject,
|
|
6371
|
+
sendReply,
|
|
6372
|
+
log: config2.log,
|
|
6373
|
+
seen
|
|
6374
|
+
};
|
|
6375
|
+
const scheduleReconnect = (delayMs) => {
|
|
6376
|
+
if (stopped) return;
|
|
6377
|
+
setTimeout(() => void connect(), delayMs).unref?.();
|
|
6378
|
+
};
|
|
6379
|
+
const connect = async () => {
|
|
6380
|
+
if (stopped) return;
|
|
6381
|
+
let url;
|
|
6382
|
+
try {
|
|
6383
|
+
const res = await fetchImpl(`${SLACK_API}/apps.connections.open`, {
|
|
6384
|
+
method: "POST",
|
|
6385
|
+
headers: { Authorization: `Bearer ${config2.appToken}` },
|
|
6386
|
+
// Bound the open call so a hung request can't wedge ingest without a
|
|
6387
|
+
// reconnect (Node 22+ required for opencode Slack ingest, so
|
|
6388
|
+
// AbortSignal.timeout is available).
|
|
6389
|
+
signal: AbortSignal.timeout(1e4)
|
|
6390
|
+
});
|
|
6391
|
+
const data = await res.json();
|
|
6392
|
+
if (!data.ok || !data.url) {
|
|
6393
|
+
config2.log(`[slack-ingest:${config2.codeName}] apps.connections.open failed: ${data.error}`);
|
|
6394
|
+
scheduleReconnect(1e4);
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6397
|
+
url = data.url;
|
|
6398
|
+
} catch (err) {
|
|
6399
|
+
config2.log(`[slack-ingest:${config2.codeName}] connect threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
6400
|
+
scheduleReconnect(1e4);
|
|
6401
|
+
return;
|
|
6402
|
+
}
|
|
6403
|
+
if (stopped) return;
|
|
6404
|
+
try {
|
|
6405
|
+
ws = socketFactory(url);
|
|
6406
|
+
ws.onopen = () => config2.log(`[slack-ingest:${config2.codeName}] Socket Mode connected`);
|
|
6407
|
+
ws.onclose = () => {
|
|
6408
|
+
if (!stopped) scheduleReconnect(5e3);
|
|
6409
|
+
};
|
|
6410
|
+
ws.onerror = () => {
|
|
6411
|
+
};
|
|
6412
|
+
ws.onmessage = (ev) => {
|
|
6413
|
+
void onEnvelope(String(ev.data));
|
|
6414
|
+
};
|
|
6415
|
+
} catch (err) {
|
|
6416
|
+
config2.log(`[slack-ingest:${config2.codeName}] socket construction threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
6417
|
+
scheduleReconnect(1e4);
|
|
6418
|
+
}
|
|
6419
|
+
};
|
|
6420
|
+
const onEnvelope = async (raw) => {
|
|
6421
|
+
let msg;
|
|
6422
|
+
try {
|
|
6423
|
+
msg = JSON.parse(raw);
|
|
6424
|
+
} catch {
|
|
6425
|
+
return;
|
|
6426
|
+
}
|
|
6427
|
+
if (msg.envelope_id && ws) {
|
|
6428
|
+
try {
|
|
6429
|
+
ws.send(JSON.stringify({ envelope_id: msg.envelope_id }));
|
|
6430
|
+
} catch {
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
if (msg.type === "disconnect") {
|
|
6434
|
+
try {
|
|
6435
|
+
ws?.close();
|
|
6436
|
+
} catch {
|
|
6437
|
+
}
|
|
6438
|
+
return;
|
|
6439
|
+
}
|
|
6440
|
+
if (msg.type !== "events_api") return;
|
|
6441
|
+
const event = msg.payload?.event;
|
|
6442
|
+
if (!event || event.type !== "message" && event.type !== "app_mention") return;
|
|
6443
|
+
try {
|
|
6444
|
+
await dispatchSlackInbound(event, ctx);
|
|
6445
|
+
} catch (err) {
|
|
6446
|
+
config2.log(`[slack-ingest:${config2.codeName}] dispatch threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
6447
|
+
}
|
|
6448
|
+
};
|
|
6449
|
+
void connect();
|
|
6450
|
+
return {
|
|
6451
|
+
stop() {
|
|
6452
|
+
stopped = true;
|
|
6453
|
+
try {
|
|
6454
|
+
ws?.close();
|
|
6455
|
+
} catch {
|
|
6456
|
+
}
|
|
6457
|
+
ws = null;
|
|
6458
|
+
}
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6462
|
+
// src/lib/opencode-slack-ingest.ts
|
|
6463
|
+
var ingests = /* @__PURE__ */ new Map();
|
|
6464
|
+
function fingerprint(params) {
|
|
6465
|
+
return createHash11("sha256").update(
|
|
6466
|
+
JSON.stringify({
|
|
6467
|
+
appToken: params.appToken,
|
|
6468
|
+
botToken: params.botToken,
|
|
6469
|
+
senderPolicy: params.senderPolicy,
|
|
6470
|
+
peerConfig: params.peerConfig
|
|
6471
|
+
})
|
|
6472
|
+
).digest("hex");
|
|
6473
|
+
}
|
|
6474
|
+
async function ensureOpencodeSlackIngest(opts) {
|
|
6475
|
+
const { codeName, agentId, log: log2 } = opts;
|
|
6476
|
+
const readEnv = opts.readEnv ?? readChannelServerEnv;
|
|
6477
|
+
let params;
|
|
6478
|
+
let env;
|
|
6479
|
+
try {
|
|
6480
|
+
env = readEnv(codeName, "slack");
|
|
6481
|
+
if (!env) {
|
|
6482
|
+
stopOpencodeSlackIngest(codeName, log2);
|
|
6483
|
+
return;
|
|
6484
|
+
}
|
|
6485
|
+
params = resolveSlackIngestParamsFromEnv(env, { agentTeamId: env["AGT_TEAM_ID"] ?? null });
|
|
6486
|
+
} catch (err) {
|
|
6487
|
+
stopOpencodeSlackIngest(codeName, log2);
|
|
6488
|
+
log2(
|
|
6489
|
+
`[slack-ingest:${codeName}] not started: ${err instanceof Error ? err.message : String(err)}`
|
|
6490
|
+
);
|
|
6491
|
+
return;
|
|
6492
|
+
}
|
|
6493
|
+
if (!params) {
|
|
6494
|
+
stopOpencodeSlackIngest(codeName, log2);
|
|
6495
|
+
return;
|
|
6496
|
+
}
|
|
6497
|
+
const key = fingerprint(params);
|
|
6498
|
+
const existing = ingests.get(codeName);
|
|
6499
|
+
if (existing?.key === key) return;
|
|
6500
|
+
const { id: botUserId, authFailed } = await resolveSlackBotUserId(params.botToken, opts.fetchImpl);
|
|
6501
|
+
if (authFailed) {
|
|
6502
|
+
stopOpencodeSlackIngest(codeName, log2);
|
|
6503
|
+
log2(`[slack-ingest:${codeName}] not started: Slack bot token rejected (needs reconnect)`);
|
|
6504
|
+
return;
|
|
6505
|
+
}
|
|
6506
|
+
if (!botUserId) {
|
|
6507
|
+
log2(`[slack-ingest:${codeName}] bot user id unresolved; echo filtering degraded`);
|
|
6508
|
+
}
|
|
6509
|
+
if (existing) {
|
|
6510
|
+
existing.handle.stop();
|
|
6511
|
+
ingests.delete(codeName);
|
|
6512
|
+
log2(`[slack-ingest:${codeName}] config changed; restarting ingest`);
|
|
6513
|
+
}
|
|
6514
|
+
const handle = startSlackIngest({
|
|
6515
|
+
codeName,
|
|
6516
|
+
agentId,
|
|
6517
|
+
appToken: params.appToken,
|
|
6518
|
+
botToken: params.botToken,
|
|
6519
|
+
botUserId,
|
|
6520
|
+
senderPolicy: params.senderPolicy,
|
|
6521
|
+
peerConfig: params.peerConfig,
|
|
6522
|
+
inject: (msg) => injectOpencodeMessage(codeName, msg, { awaitReply: true }),
|
|
6523
|
+
log: log2,
|
|
6524
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
|
|
6525
|
+
...opts.socketFactory ? { socketFactory: opts.socketFactory } : {}
|
|
6526
|
+
});
|
|
6527
|
+
ingests.set(codeName, { key, handle });
|
|
6528
|
+
log2(`[slack-ingest:${codeName}] ingest started (policy=${params.senderPolicy.mode})`);
|
|
6529
|
+
}
|
|
6530
|
+
function stopOpencodeSlackIngest(codeName, log2) {
|
|
6531
|
+
const entry = ingests.get(codeName);
|
|
6532
|
+
if (!entry) return;
|
|
6533
|
+
entry.handle.stop();
|
|
6534
|
+
ingests.delete(codeName);
|
|
6535
|
+
log2?.(`[slack-ingest:${codeName}] ingest stopped`);
|
|
6536
|
+
}
|
|
6537
|
+
function stopAllOpencodeSlackIngests(log2) {
|
|
6538
|
+
for (const codeName of [...ingests.keys()]) stopOpencodeSlackIngest(codeName, log2);
|
|
6539
|
+
}
|
|
6540
|
+
|
|
5350
6541
|
// src/lib/wedge-detection.ts
|
|
5351
6542
|
var DEFAULTS = {
|
|
5352
6543
|
inboundWaitSeconds: 120,
|
|
@@ -5487,24 +6678,24 @@ function partitionActionableByPoison(actionable, states, config2) {
|
|
|
5487
6678
|
}
|
|
5488
6679
|
|
|
5489
6680
|
// src/lib/restart-flags.ts
|
|
5490
|
-
import { existsSync as
|
|
5491
|
-
import { homedir as
|
|
5492
|
-
import { join as
|
|
6681
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync16, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
6682
|
+
import { homedir as homedir10 } from "os";
|
|
6683
|
+
import { join as join19 } from "path";
|
|
5493
6684
|
import { randomUUID } from "crypto";
|
|
5494
6685
|
function restartFlagsDir() {
|
|
5495
|
-
return
|
|
6686
|
+
return join19(homedir10(), ".augmented", "restart-flags");
|
|
5496
6687
|
}
|
|
5497
6688
|
function flagPath(codeName) {
|
|
5498
|
-
return
|
|
6689
|
+
return join19(restartFlagsDir(), `${codeName}.flag`);
|
|
5499
6690
|
}
|
|
5500
6691
|
function readRestartFlags() {
|
|
5501
6692
|
const dir = restartFlagsDir();
|
|
5502
|
-
if (!
|
|
6693
|
+
if (!existsSync10(dir)) return [];
|
|
5503
6694
|
const out = [];
|
|
5504
6695
|
for (const entry of readdirSync4(dir)) {
|
|
5505
6696
|
if (!entry.endsWith(".flag")) continue;
|
|
5506
6697
|
try {
|
|
5507
|
-
const raw =
|
|
6698
|
+
const raw = readFileSync16(join19(dir, entry), "utf8");
|
|
5508
6699
|
const parsed = JSON.parse(raw);
|
|
5509
6700
|
if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
|
|
5510
6701
|
parsed.codeName = entry.replace(/\.flag$/, "");
|
|
@@ -5522,7 +6713,7 @@ function readRestartFlags() {
|
|
|
5522
6713
|
}
|
|
5523
6714
|
function deleteRestartFlag(codeName) {
|
|
5524
6715
|
const path = flagPath(codeName);
|
|
5525
|
-
if (
|
|
6716
|
+
if (existsSync10(path)) {
|
|
5526
6717
|
rmSync3(path, { force: true });
|
|
5527
6718
|
}
|
|
5528
6719
|
}
|
|
@@ -5868,10 +7059,10 @@ function startRealtimeConfig(config2) {
|
|
|
5868
7059
|
filter: filterStr
|
|
5869
7060
|
}, (payload) => {
|
|
5870
7061
|
const agent = payload.new;
|
|
5871
|
-
const
|
|
7062
|
+
const fingerprint2 = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
|
|
5872
7063
|
const prev = lastKnown.get(agent.agent_id);
|
|
5873
|
-
if (prev ===
|
|
5874
|
-
lastKnown.set(agent.agent_id,
|
|
7064
|
+
if (prev === fingerprint2) return;
|
|
7065
|
+
lastKnown.set(agent.agent_id, fingerprint2);
|
|
5875
7066
|
log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
|
|
5876
7067
|
onConfigChange(agent);
|
|
5877
7068
|
}).subscribe((status) => {
|
|
@@ -6739,20 +7930,21 @@ function isHostBusyForForcedUpdate() {
|
|
|
6739
7930
|
return false;
|
|
6740
7931
|
}
|
|
6741
7932
|
var runningMcpHashes = /* @__PURE__ */ new Map();
|
|
7933
|
+
var anchorSessionLifecycle = new AnchorSessionLifecycle();
|
|
6742
7934
|
var runningMcpServerKeys = /* @__PURE__ */ new Map();
|
|
6743
7935
|
var runningChannelSecretHashes = /* @__PURE__ */ new Map();
|
|
6744
7936
|
var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
|
|
6745
7937
|
function projectMcpHash(_codeName, projectDir) {
|
|
6746
7938
|
try {
|
|
6747
|
-
const raw =
|
|
6748
|
-
return
|
|
7939
|
+
const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
|
|
7940
|
+
return createHash12("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
6749
7941
|
} catch {
|
|
6750
7942
|
return null;
|
|
6751
7943
|
}
|
|
6752
7944
|
}
|
|
6753
7945
|
function projectMcpKeys(_codeName, projectDir) {
|
|
6754
7946
|
try {
|
|
6755
|
-
const raw =
|
|
7947
|
+
const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
|
|
6756
7948
|
const parsed = JSON.parse(raw);
|
|
6757
7949
|
const servers = parsed.mcpServers;
|
|
6758
7950
|
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
@@ -6770,7 +7962,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
|
|
|
6770
7962
|
else runningMcpServerKeys.delete(codeName);
|
|
6771
7963
|
let launchStructure = null;
|
|
6772
7964
|
try {
|
|
6773
|
-
const raw =
|
|
7965
|
+
const raw = readFileSync17(join20(projectDir, ".mcp.json"), "utf-8");
|
|
6774
7966
|
launchStructure = managedMcpStructureHashFromFile(
|
|
6775
7967
|
JSON.parse(raw),
|
|
6776
7968
|
isManagedMcpServerKey
|
|
@@ -6870,7 +8062,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
|
|
|
6870
8062
|
if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
|
|
6871
8063
|
let mcpJsonForRebind = null;
|
|
6872
8064
|
try {
|
|
6873
|
-
mcpJsonForRebind = JSON.parse(
|
|
8065
|
+
mcpJsonForRebind = JSON.parse(readFileSync17(join20(projectDir, ".mcp.json"), "utf-8"));
|
|
6874
8066
|
} catch {
|
|
6875
8067
|
mcpJsonForRebind = null;
|
|
6876
8068
|
}
|
|
@@ -6999,7 +8191,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
|
6999
8191
|
function projectChannelSecretHash(projectDir) {
|
|
7000
8192
|
try {
|
|
7001
8193
|
const entries = parseEnvIntegrations(
|
|
7002
|
-
|
|
8194
|
+
readFileSync17(join20(projectDir, ".env.integrations"), "utf-8")
|
|
7003
8195
|
);
|
|
7004
8196
|
return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
|
|
7005
8197
|
} catch {
|
|
@@ -7061,6 +8253,7 @@ function resolveAgentFramework(codeName) {
|
|
|
7061
8253
|
function clearAgentCaches(agentId, codeName) {
|
|
7062
8254
|
const channelCacheMutated = clearAgentState(agentId, codeName);
|
|
7063
8255
|
agentChannelTokens.delete(codeName);
|
|
8256
|
+
stopOpencodeSlackIngest(codeName, log);
|
|
7064
8257
|
agentFrameworkCache.delete(codeName);
|
|
7065
8258
|
kanbanBoardCache.delete(codeName);
|
|
7066
8259
|
notifyBoardCache.delete(codeName);
|
|
@@ -7087,7 +8280,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
7087
8280
|
var lastVersionCheckAt = 0;
|
|
7088
8281
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
7089
8282
|
var lastResponsivenessProbeAt = 0;
|
|
7090
|
-
var agtCliVersion = true ? "0.28.
|
|
8283
|
+
var agtCliVersion = true ? "0.28.320" : "dev";
|
|
7091
8284
|
function resolveBrewPath(execFileSync2) {
|
|
7092
8285
|
try {
|
|
7093
8286
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -7100,7 +8293,7 @@ function resolveBrewPath(execFileSync2) {
|
|
|
7100
8293
|
"/usr/local/bin/brew"
|
|
7101
8294
|
];
|
|
7102
8295
|
for (const path of fallbacks) {
|
|
7103
|
-
if (
|
|
8296
|
+
if (existsSync11(path)) return path;
|
|
7104
8297
|
}
|
|
7105
8298
|
return null;
|
|
7106
8299
|
}
|
|
@@ -7110,7 +8303,7 @@ function claudeBinaryInstalled(execFileSync2) {
|
|
|
7110
8303
|
"/opt/homebrew/bin/claude",
|
|
7111
8304
|
"/usr/local/bin/claude"
|
|
7112
8305
|
];
|
|
7113
|
-
if (canonical.some((path) =>
|
|
8306
|
+
if (canonical.some((path) => existsSync11(path))) return true;
|
|
7114
8307
|
try {
|
|
7115
8308
|
execFileSync2("which", ["claude"], { timeout: 5e3 });
|
|
7116
8309
|
return true;
|
|
@@ -7146,7 +8339,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
7146
8339
|
}
|
|
7147
8340
|
const { binary, installer, package: pkg, script } = integration.cli_tool;
|
|
7148
8341
|
const resolvedInstaller = installer ?? "manual";
|
|
7149
|
-
const { execFileSync: execFileSync2, execSync } = await import("child_process");
|
|
8342
|
+
const { execFileSync: execFileSync2, execSync: execSync2 } = await import("child_process");
|
|
7150
8343
|
try {
|
|
7151
8344
|
execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
|
|
7152
8345
|
toolkitCliEnsured.add(toolkitSlug);
|
|
@@ -7197,7 +8390,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
7197
8390
|
return;
|
|
7198
8391
|
}
|
|
7199
8392
|
log(`[toolkit-install] ${toolkitSlug}: running declared install script\u2026`);
|
|
7200
|
-
|
|
8393
|
+
execSync2(script, { timeout: 18e4, stdio: "pipe" });
|
|
7201
8394
|
}
|
|
7202
8395
|
} catch (err) {
|
|
7203
8396
|
const e = err;
|
|
@@ -7221,8 +8414,8 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
7221
8414
|
}
|
|
7222
8415
|
function runAsync(cmd, args, opts) {
|
|
7223
8416
|
return new Promise((resolve, reject) => {
|
|
7224
|
-
import("child_process").then(({ spawn }) => {
|
|
7225
|
-
const child =
|
|
8417
|
+
import("child_process").then(({ spawn: spawn2 }) => {
|
|
8418
|
+
const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"], cwd: opts.cwd });
|
|
7226
8419
|
let stdout = "";
|
|
7227
8420
|
let stderr = "";
|
|
7228
8421
|
let settled = false;
|
|
@@ -7265,8 +8458,8 @@ function claudeManagedSettingsPath() {
|
|
|
7265
8458
|
function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
7266
8459
|
try {
|
|
7267
8460
|
let settings = {};
|
|
7268
|
-
if (
|
|
7269
|
-
const raw =
|
|
8461
|
+
if (existsSync11(path)) {
|
|
8462
|
+
const raw = readFileSync17(path, "utf-8").trim();
|
|
7270
8463
|
if (raw) {
|
|
7271
8464
|
let parsed;
|
|
7272
8465
|
try {
|
|
@@ -7282,7 +8475,7 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
|
7282
8475
|
}
|
|
7283
8476
|
if (settings.channelsEnabled === true) return "ok";
|
|
7284
8477
|
settings.channelsEnabled = true;
|
|
7285
|
-
|
|
8478
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
7286
8479
|
writeFileSync8(path, `${JSON.stringify(settings, null, 2)}
|
|
7287
8480
|
`);
|
|
7288
8481
|
log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
|
|
@@ -7328,7 +8521,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
7328
8521
|
if (!process.env.PATH?.split(":").includes(brewBinDir)) {
|
|
7329
8522
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
7330
8523
|
}
|
|
7331
|
-
if (
|
|
8524
|
+
if (existsSync11("/home/linuxbrew/.linuxbrew/bin/claude")) {
|
|
7332
8525
|
log("Claude Code installed successfully");
|
|
7333
8526
|
} else {
|
|
7334
8527
|
log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
|
|
@@ -7384,7 +8577,7 @@ ${r.stderr}`;
|
|
|
7384
8577
|
}
|
|
7385
8578
|
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
7386
8579
|
function selfUpdateAppliedMarkerPath() {
|
|
7387
|
-
return
|
|
8580
|
+
return join20(homedir11(), ".augmented", ".last-self-update-applied");
|
|
7388
8581
|
}
|
|
7389
8582
|
var selfUpdateUpToDateLogged = false;
|
|
7390
8583
|
var restartAfterUpgrade = false;
|
|
@@ -7408,7 +8601,7 @@ async function checkAndUpdateCli(opts) {
|
|
|
7408
8601
|
const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
|
|
7409
8602
|
if (!isBrewFormula && !isNpmGlobal) return "noop";
|
|
7410
8603
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
7411
|
-
const markerPath =
|
|
8604
|
+
const markerPath = join20(homedir11(), ".augmented", ".last-update-check");
|
|
7412
8605
|
if (!force) {
|
|
7413
8606
|
try {
|
|
7414
8607
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
@@ -7686,13 +8879,13 @@ async function checkClaudeAuth() {
|
|
|
7686
8879
|
}
|
|
7687
8880
|
var evalEmptyMcpConfigPath = null;
|
|
7688
8881
|
function ensureEvalEmptyMcpConfig() {
|
|
7689
|
-
if (evalEmptyMcpConfigPath &&
|
|
7690
|
-
const dir =
|
|
8882
|
+
if (evalEmptyMcpConfigPath && existsSync11(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
|
|
8883
|
+
const dir = join20(homedir11(), ".augmented");
|
|
7691
8884
|
try {
|
|
7692
|
-
|
|
8885
|
+
mkdirSync8(dir, { recursive: true });
|
|
7693
8886
|
} catch {
|
|
7694
8887
|
}
|
|
7695
|
-
const p =
|
|
8888
|
+
const p = join20(dir, ".eval-empty-mcp.json");
|
|
7696
8889
|
writeFileSync8(p, JSON.stringify({ mcpServers: {} }));
|
|
7697
8890
|
evalEmptyMcpConfigPath = p;
|
|
7698
8891
|
return p;
|
|
@@ -7718,7 +8911,7 @@ async function runEvalClaude(prompt, model) {
|
|
|
7718
8911
|
""
|
|
7719
8912
|
];
|
|
7720
8913
|
const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), args, {
|
|
7721
|
-
cwd:
|
|
8914
|
+
cwd: homedir11(),
|
|
7722
8915
|
timeout: 12e4,
|
|
7723
8916
|
stdin: "ignore",
|
|
7724
8917
|
env: childEnv,
|
|
@@ -7784,10 +8977,10 @@ function resolveConversationEvalBackend() {
|
|
|
7784
8977
|
return conversationEvalBackend;
|
|
7785
8978
|
}
|
|
7786
8979
|
function getStateFile() {
|
|
7787
|
-
return
|
|
8980
|
+
return join20(config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
|
|
7788
8981
|
}
|
|
7789
8982
|
function channelHashCacheDir() {
|
|
7790
|
-
return config?.configDir ??
|
|
8983
|
+
return config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
7791
8984
|
}
|
|
7792
8985
|
function loadChannelHashCache2() {
|
|
7793
8986
|
loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
|
|
@@ -7815,7 +9008,7 @@ function removeSenderPolicyBaselineEntry(agentId) {
|
|
|
7815
9008
|
var _channelQuarantineStore = null;
|
|
7816
9009
|
function channelQuarantineStore() {
|
|
7817
9010
|
if (!_channelQuarantineStore) {
|
|
7818
|
-
const dir = config?.configDir ??
|
|
9011
|
+
const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
7819
9012
|
_channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
|
|
7820
9013
|
}
|
|
7821
9014
|
return _channelQuarantineStore;
|
|
@@ -7823,7 +9016,7 @@ function channelQuarantineStore() {
|
|
|
7823
9016
|
var _hostFlagStore = null;
|
|
7824
9017
|
function hostFlagStore() {
|
|
7825
9018
|
if (!_hostFlagStore) {
|
|
7826
|
-
const dir = config?.configDir ??
|
|
9019
|
+
const dir = config?.configDir ?? join20(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
7827
9020
|
_hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
|
|
7828
9021
|
}
|
|
7829
9022
|
return _hostFlagStore;
|
|
@@ -7891,12 +9084,12 @@ function parseSkillFrontmatter(content) {
|
|
|
7891
9084
|
}
|
|
7892
9085
|
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
7893
9086
|
const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync9 } = await import("fs");
|
|
7894
|
-
const skillsDir =
|
|
7895
|
-
const claudeMdPath =
|
|
9087
|
+
const skillsDir = join20(configDir, codeName, "project", ".claude", "skills");
|
|
9088
|
+
const claudeMdPath = join20(configDir, codeName, "project", "CLAUDE.md");
|
|
7896
9089
|
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
7897
9090
|
const entries = [];
|
|
7898
9091
|
for (const dir of readdirSync6(skillsDir).sort()) {
|
|
7899
|
-
const skillFile =
|
|
9092
|
+
const skillFile = join20(skillsDir, dir, "SKILL.md");
|
|
7900
9093
|
if (!ex(skillFile)) continue;
|
|
7901
9094
|
try {
|
|
7902
9095
|
const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
|
|
@@ -7959,7 +9152,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
7959
9152
|
if (codeNames.length === 0) return;
|
|
7960
9153
|
void (async () => {
|
|
7961
9154
|
try {
|
|
7962
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
9155
|
+
const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
|
|
7963
9156
|
await api.post("/host/heartbeat", {
|
|
7964
9157
|
host_id: hostId,
|
|
7965
9158
|
agent_diagnostics: collectDiagnostics(codeNames)
|
|
@@ -8057,7 +9250,7 @@ async function pollCycle() {
|
|
|
8057
9250
|
}
|
|
8058
9251
|
try {
|
|
8059
9252
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
8060
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
9253
|
+
const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
|
|
8061
9254
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
8062
9255
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
8063
9256
|
let tailscaleHostname;
|
|
@@ -8078,8 +9271,8 @@ async function pollCycle() {
|
|
|
8078
9271
|
}
|
|
8079
9272
|
let osUsername;
|
|
8080
9273
|
try {
|
|
8081
|
-
const { userInfo } = await import("os");
|
|
8082
|
-
osUsername =
|
|
9274
|
+
const { userInfo: userInfo2 } = await import("os");
|
|
9275
|
+
osUsername = userInfo2().username;
|
|
8083
9276
|
} catch {
|
|
8084
9277
|
}
|
|
8085
9278
|
let claudeAuth = null;
|
|
@@ -8087,7 +9280,7 @@ async function pollCycle() {
|
|
|
8087
9280
|
claudeAuth = await detectClaudeAuth();
|
|
8088
9281
|
} catch (err) {
|
|
8089
9282
|
const errText = err instanceof Error ? err.message : String(err);
|
|
8090
|
-
const errId =
|
|
9283
|
+
const errId = createHash12("sha256").update(errText).digest("hex").slice(0, 12);
|
|
8091
9284
|
log(`Claude auth detection failed (error_id=${errId})`);
|
|
8092
9285
|
}
|
|
8093
9286
|
const hostHasClaudeCode = state6.agents.some(
|
|
@@ -8206,7 +9399,7 @@ async function pollCycle() {
|
|
|
8206
9399
|
const {
|
|
8207
9400
|
collectResponsivenessProbes,
|
|
8208
9401
|
getResponsivenessIntervalMs
|
|
8209
|
-
} = await import("../responsiveness-probe-
|
|
9402
|
+
} = await import("../responsiveness-probe-NARLJJGH.js");
|
|
8210
9403
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
8211
9404
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
8212
9405
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -8238,7 +9431,7 @@ async function pollCycle() {
|
|
|
8238
9431
|
collectResponsivenessProbes,
|
|
8239
9432
|
livePendingInboundOldestAgeSeconds,
|
|
8240
9433
|
parkPendingInbound
|
|
8241
|
-
} = await import("../responsiveness-probe-
|
|
9434
|
+
} = await import("../responsiveness-probe-NARLJJGH.js");
|
|
8242
9435
|
const { getProjectDir: wedgeProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
|
|
8243
9436
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
8244
9437
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -8327,13 +9520,13 @@ async function pollCycle() {
|
|
|
8327
9520
|
);
|
|
8328
9521
|
if (hostFlagStore().getBoolean("wedge-transient-notice")) {
|
|
8329
9522
|
try {
|
|
8330
|
-
const paneTail =
|
|
9523
|
+
const paneTail = readFileSync17(paneLogPath(codeName), "utf8").slice(-65536);
|
|
8331
9524
|
const transient = detectTransientApiErrorInLog(paneTail);
|
|
8332
9525
|
if (transient) {
|
|
8333
|
-
const wedgeHome =
|
|
8334
|
-
if (
|
|
9526
|
+
const wedgeHome = join20(homedir11(), ".augmented", codeName);
|
|
9527
|
+
if (existsSync11(wedgeHome)) {
|
|
8335
9528
|
atomicWriteFileSync(
|
|
8336
|
-
|
|
9529
|
+
join20(wedgeHome, "watchdog-give-up.json"),
|
|
8337
9530
|
JSON.stringify({
|
|
8338
9531
|
gave_up_at: wedgeNow.toISOString(),
|
|
8339
9532
|
reason: "transient_overload"
|
|
@@ -8582,7 +9775,7 @@ async function pollCycle() {
|
|
|
8582
9775
|
} catch {
|
|
8583
9776
|
}
|
|
8584
9777
|
killAgentChannelProcesses(prev.codeName, { log });
|
|
8585
|
-
const agentDir =
|
|
9778
|
+
const agentDir = join20(adapter.getAgentDir(prev.codeName), "provision");
|
|
8586
9779
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
8587
9780
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
8588
9781
|
}
|
|
@@ -8669,10 +9862,10 @@ async function pollCycle() {
|
|
|
8669
9862
|
// pending-inbound marker. Best-effort: a write failure is logged by
|
|
8670
9863
|
// the watchdog, never fails the poll cycle.
|
|
8671
9864
|
signalGiveUp: (codeName) => {
|
|
8672
|
-
const dir =
|
|
8673
|
-
if (!
|
|
9865
|
+
const dir = join20(homedir11(), ".augmented", codeName);
|
|
9866
|
+
if (!existsSync11(dir)) return;
|
|
8674
9867
|
atomicWriteFileSync(
|
|
8675
|
-
|
|
9868
|
+
join20(dir, "watchdog-give-up.json"),
|
|
8676
9869
|
JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
8677
9870
|
);
|
|
8678
9871
|
}
|
|
@@ -8802,7 +9995,7 @@ async function processAgent(agent, agentStates) {
|
|
|
8802
9995
|
}
|
|
8803
9996
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8804
9997
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
8805
|
-
let agentDir =
|
|
9998
|
+
let agentDir = join20(adapter.getAgentDir(agent.code_name), "provision");
|
|
8806
9999
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
8807
10000
|
if (previousKnownStatus !== agent.status) {
|
|
8808
10001
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -8848,7 +10041,7 @@ async function processAgent(agent, agentStates) {
|
|
|
8848
10041
|
const residuals = {
|
|
8849
10042
|
gatewayRunning: false,
|
|
8850
10043
|
portAllocated: false,
|
|
8851
|
-
provisionDirExists:
|
|
10044
|
+
provisionDirExists: existsSync11(agentDir)
|
|
8852
10045
|
};
|
|
8853
10046
|
if (!hasRevokedResiduals(residuals)) {
|
|
8854
10047
|
agentStates.push({
|
|
@@ -8986,7 +10179,7 @@ async function processAgent(agent, agentStates) {
|
|
|
8986
10179
|
const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
|
|
8987
10180
|
agentFrameworkCache.set(agent.code_name, frameworkId);
|
|
8988
10181
|
const frameworkAdapter = getFramework(frameworkId);
|
|
8989
|
-
agentDir =
|
|
10182
|
+
agentDir = join20(frameworkAdapter.getAgentDir(agent.code_name), "provision");
|
|
8990
10183
|
cacheAgentDeliveryMetadata(agent.code_name, refreshData);
|
|
8991
10184
|
agentRestartTimezoneInputs.set(agent.code_name, {
|
|
8992
10185
|
agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
|
|
@@ -9031,9 +10224,9 @@ async function processAgent(agent, agentStates) {
|
|
|
9031
10224
|
try {
|
|
9032
10225
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
9033
10226
|
const changedFiles = [];
|
|
9034
|
-
|
|
10227
|
+
mkdirSync8(agentDir, { recursive: true });
|
|
9035
10228
|
for (const artifact of artifacts) {
|
|
9036
|
-
const filePath =
|
|
10229
|
+
const filePath = join20(agentDir, artifact.relativePath);
|
|
9037
10230
|
let existingHash;
|
|
9038
10231
|
let newHash;
|
|
9039
10232
|
let writeContent = artifact.content;
|
|
@@ -9052,8 +10245,8 @@ async function processAgent(agent, agentStates) {
|
|
|
9052
10245
|
};
|
|
9053
10246
|
newHash = sha256(stripDynamicSections(artifact.content));
|
|
9054
10247
|
try {
|
|
9055
|
-
const projectClaudeMd =
|
|
9056
|
-
const existing =
|
|
10248
|
+
const projectClaudeMd = join20(config.configDir, agent.code_name, "project", "CLAUDE.md");
|
|
10249
|
+
const existing = readFileSync17(projectClaudeMd, "utf-8");
|
|
9057
10250
|
existingHash = sha256(stripDynamicSections(existing));
|
|
9058
10251
|
} catch {
|
|
9059
10252
|
existingHash = null;
|
|
@@ -9071,7 +10264,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9071
10264
|
const generatorKeys = Object.keys(generatorServers);
|
|
9072
10265
|
let existingRaw = "";
|
|
9073
10266
|
try {
|
|
9074
|
-
existingRaw =
|
|
10267
|
+
existingRaw = readFileSync17(filePath, "utf-8");
|
|
9075
10268
|
} catch {
|
|
9076
10269
|
}
|
|
9077
10270
|
const existingServers = parseMcp(existingRaw);
|
|
@@ -9093,13 +10286,13 @@ async function processAgent(agent, agentStates) {
|
|
|
9093
10286
|
}
|
|
9094
10287
|
}
|
|
9095
10288
|
if (changedFiles.length > 0) {
|
|
9096
|
-
const isFirst = !
|
|
10289
|
+
const isFirst = !existsSync11(join20(agentDir, "CHARTER.md"));
|
|
9097
10290
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
9098
10291
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
9099
10292
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
9100
10293
|
for (const file of changedFiles) {
|
|
9101
|
-
const filePath =
|
|
9102
|
-
|
|
10294
|
+
const filePath = join20(agentDir, file.relativePath);
|
|
10295
|
+
mkdirSync8(dirname6(filePath), { recursive: true });
|
|
9103
10296
|
if (file.relativePath === ".mcp.json") {
|
|
9104
10297
|
safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
|
|
9105
10298
|
} else {
|
|
@@ -9107,12 +10300,12 @@ async function processAgent(agent, agentStates) {
|
|
|
9107
10300
|
}
|
|
9108
10301
|
}
|
|
9109
10302
|
try {
|
|
9110
|
-
const provSkillsDir =
|
|
9111
|
-
if (
|
|
10303
|
+
const provSkillsDir = join20(agentDir, ".claude", "skills");
|
|
10304
|
+
if (existsSync11(provSkillsDir)) {
|
|
9112
10305
|
for (const folder of readdirSync5(provSkillsDir)) {
|
|
9113
10306
|
if (folder.startsWith("knowledge-")) {
|
|
9114
10307
|
try {
|
|
9115
|
-
rmSync4(
|
|
10308
|
+
rmSync4(join20(provSkillsDir, folder), { recursive: true });
|
|
9116
10309
|
} catch {
|
|
9117
10310
|
}
|
|
9118
10311
|
}
|
|
@@ -9125,7 +10318,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9125
10318
|
const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
|
|
9126
10319
|
const hashes = /* @__PURE__ */ new Map();
|
|
9127
10320
|
for (const file of trackedFiles2) {
|
|
9128
|
-
const h = hashFile(
|
|
10321
|
+
const h = hashFile(join20(agentDir, file));
|
|
9129
10322
|
if (h) hashes.set(file, h);
|
|
9130
10323
|
}
|
|
9131
10324
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -9143,14 +10336,14 @@ async function processAgent(agent, agentStates) {
|
|
|
9143
10336
|
}
|
|
9144
10337
|
if (Array.isArray(refreshData.workflows)) {
|
|
9145
10338
|
try {
|
|
9146
|
-
const provWorkflowsDir =
|
|
9147
|
-
if (
|
|
10339
|
+
const provWorkflowsDir = join20(agentDir, ".claude", "workflows");
|
|
10340
|
+
if (existsSync11(provWorkflowsDir)) {
|
|
9148
10341
|
const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
|
|
9149
10342
|
for (const file of readdirSync5(provWorkflowsDir)) {
|
|
9150
10343
|
if (!file.endsWith(".js")) continue;
|
|
9151
10344
|
if (expected.has(file)) continue;
|
|
9152
10345
|
try {
|
|
9153
|
-
rmSync4(
|
|
10346
|
+
rmSync4(join20(provWorkflowsDir, file));
|
|
9154
10347
|
} catch {
|
|
9155
10348
|
}
|
|
9156
10349
|
}
|
|
@@ -9207,10 +10400,10 @@ async function processAgent(agent, agentStates) {
|
|
|
9207
10400
|
}
|
|
9208
10401
|
let lastDriftCheckAt = now;
|
|
9209
10402
|
const written = agentState.writtenHashes.get(agent.agent_id);
|
|
9210
|
-
if (written &&
|
|
10403
|
+
if (written && existsSync11(agentDir)) {
|
|
9211
10404
|
const driftedFiles = [];
|
|
9212
10405
|
for (const [file, expectedHash] of written) {
|
|
9213
|
-
const localHash = hashFile(
|
|
10406
|
+
const localHash = hashFile(join20(agentDir, file));
|
|
9214
10407
|
if (localHash && localHash !== expectedHash) {
|
|
9215
10408
|
driftedFiles.push(file);
|
|
9216
10409
|
}
|
|
@@ -9221,7 +10414,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9221
10414
|
try {
|
|
9222
10415
|
const localHashes = {};
|
|
9223
10416
|
for (const file of driftedFiles) {
|
|
9224
|
-
localHashes[file] = hashFile(
|
|
10417
|
+
localHashes[file] = hashFile(join20(agentDir, file));
|
|
9225
10418
|
}
|
|
9226
10419
|
await api.post("/host/drift", {
|
|
9227
10420
|
agent_id: agent.agent_id,
|
|
@@ -9408,15 +10601,15 @@ async function processAgent(agent, agentStates) {
|
|
|
9408
10601
|
const addedChannels = [...restartDecision.added];
|
|
9409
10602
|
const writeDmNoticeMarkers = isChannelAddRestart ? () => {
|
|
9410
10603
|
try {
|
|
9411
|
-
const agentAugmentedDir =
|
|
9412
|
-
|
|
10604
|
+
const agentAugmentedDir = join20(homedir11(), ".augmented", agent.code_name);
|
|
10605
|
+
mkdirSync8(agentAugmentedDir, { recursive: true });
|
|
9413
10606
|
const markerJson = JSON.stringify({
|
|
9414
10607
|
version: 1,
|
|
9415
10608
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9416
10609
|
added: addedChannels
|
|
9417
10610
|
});
|
|
9418
10611
|
for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
|
|
9419
|
-
atomicWriteFileSync(
|
|
10612
|
+
atomicWriteFileSync(join20(agentAugmentedDir, file), markerJson);
|
|
9420
10613
|
}
|
|
9421
10614
|
} catch (err) {
|
|
9422
10615
|
log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
|
|
@@ -9492,7 +10685,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9492
10685
|
const behaviourSubset = extractMsTeamsBehaviourSubset(
|
|
9493
10686
|
msteamsEntry?.config
|
|
9494
10687
|
);
|
|
9495
|
-
const behaviourHash =
|
|
10688
|
+
const behaviourHash = createHash12("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
|
|
9496
10689
|
const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
|
|
9497
10690
|
const behaviourDecision = decideSenderPolicyRestart({
|
|
9498
10691
|
previousHash: prevBehaviourHash,
|
|
@@ -9545,7 +10738,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9545
10738
|
const slackBehaviourSubset = extractSlackBehaviourSubset(
|
|
9546
10739
|
slackEntry?.config
|
|
9547
10740
|
);
|
|
9548
|
-
const slackBehaviourHash =
|
|
10741
|
+
const slackBehaviourHash = createHash12("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
|
|
9549
10742
|
const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
|
|
9550
10743
|
const slackBehaviourDecision = decideSenderPolicyRestart({
|
|
9551
10744
|
previousHash: prevSlackBehaviourHash,
|
|
@@ -9595,24 +10788,24 @@ async function processAgent(agent, agentStates) {
|
|
|
9595
10788
|
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
|
|
9596
10789
|
try {
|
|
9597
10790
|
const agentProvisionDir = agentDir;
|
|
9598
|
-
const projectDir =
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
const provisionMcpPath =
|
|
9602
|
-
const projectMcpPath =
|
|
10791
|
+
const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
|
|
10792
|
+
mkdirSync8(agentProvisionDir, { recursive: true });
|
|
10793
|
+
mkdirSync8(projectDir, { recursive: true });
|
|
10794
|
+
const provisionMcpPath = join20(agentProvisionDir, ".mcp.json");
|
|
10795
|
+
const projectMcpPath = join20(projectDir, ".mcp.json");
|
|
9603
10796
|
let mcpConfig = { mcpServers: {} };
|
|
9604
10797
|
try {
|
|
9605
|
-
mcpConfig = JSON.parse(
|
|
10798
|
+
mcpConfig = JSON.parse(readFileSync17(provisionMcpPath, "utf-8"));
|
|
9606
10799
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
9607
10800
|
} catch {
|
|
9608
10801
|
}
|
|
9609
|
-
const localDirectChatChannel =
|
|
10802
|
+
const localDirectChatChannel = join20(homedir11(), ".augmented", "_mcp", "direct-chat-channel.js");
|
|
9610
10803
|
const directChatTeamSettings = refreshData.team?.settings;
|
|
9611
10804
|
const directChatTz = (() => {
|
|
9612
10805
|
const tz = directChatTeamSettings?.["timezone"];
|
|
9613
10806
|
return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
|
|
9614
10807
|
})();
|
|
9615
|
-
if (
|
|
10808
|
+
if (existsSync11(localDirectChatChannel)) {
|
|
9616
10809
|
const directChatEnv = {
|
|
9617
10810
|
AGT_HOST: requireHost(),
|
|
9618
10811
|
// ENG-5901 Track D: templated — the manager exports the real
|
|
@@ -9632,7 +10825,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9632
10825
|
// ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
|
|
9633
10826
|
// returns the agent root (NOT the /provision subdir `agentDir` points at),
|
|
9634
10827
|
// so it byte-matches the broker readers' path.
|
|
9635
|
-
AGT_TURN_INITIATOR_FILE:
|
|
10828
|
+
AGT_TURN_INITIATOR_FILE: join20(
|
|
9636
10829
|
frameworkAdapter.getAgentDir(agent.code_name),
|
|
9637
10830
|
".current-turn-initiator.json"
|
|
9638
10831
|
)
|
|
@@ -9652,8 +10845,8 @@ async function processAgent(agent, agentStates) {
|
|
|
9652
10845
|
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
9653
10846
|
}
|
|
9654
10847
|
}
|
|
9655
|
-
const staleChannelsPath =
|
|
9656
|
-
if (
|
|
10848
|
+
const staleChannelsPath = join20(projectDir, ".mcp-channels.json");
|
|
10849
|
+
if (existsSync11(staleChannelsPath)) {
|
|
9657
10850
|
try {
|
|
9658
10851
|
rmSync4(staleChannelsPath, { force: true });
|
|
9659
10852
|
} catch {
|
|
@@ -9722,9 +10915,27 @@ async function processAgent(agent, agentStates) {
|
|
|
9722
10915
|
log(`OAuth token refresh failed for '${agent.code_name}/${integration.definition_id}': ${err.message}`);
|
|
9723
10916
|
}
|
|
9724
10917
|
}
|
|
10918
|
+
const anchorIntegration = integrations.find((i) => i.definition_id === "anchor-browser");
|
|
10919
|
+
if (anchorIntegration) {
|
|
10920
|
+
const profileName = anchorIntegration.config?.["profile_name"];
|
|
10921
|
+
const apiKey = anchorIntegration.credentials?.["api_key"];
|
|
10922
|
+
const sessionId = await anchorSessionLifecycle.ensure({
|
|
10923
|
+
agentId: agent.agent_id,
|
|
10924
|
+
profileName,
|
|
10925
|
+
apiKey,
|
|
10926
|
+
now: Date.now(),
|
|
10927
|
+
log
|
|
10928
|
+
});
|
|
10929
|
+
const config2 = { ...anchorIntegration.config ?? {} };
|
|
10930
|
+
if (sessionId) config2["session_id"] = sessionId;
|
|
10931
|
+
else delete config2["session_id"];
|
|
10932
|
+
anchorIntegration.config = config2;
|
|
10933
|
+
} else {
|
|
10934
|
+
anchorSessionLifecycle.forget(agent.agent_id, void 0, log);
|
|
10935
|
+
}
|
|
9725
10936
|
if (hostFlagStore().getBoolean("connectivity-probe")) {
|
|
9726
10937
|
try {
|
|
9727
|
-
const probeProjectDir =
|
|
10938
|
+
const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
|
|
9728
10939
|
await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
|
|
9729
10940
|
} catch (err) {
|
|
9730
10941
|
log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -9735,7 +10946,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9735
10946
|
const forceDue = attemptsLeft > 0;
|
|
9736
10947
|
let probeRan = false;
|
|
9737
10948
|
try {
|
|
9738
|
-
const probeProjectDir =
|
|
10949
|
+
const probeProjectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
|
|
9739
10950
|
probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
|
|
9740
10951
|
} catch (err) {
|
|
9741
10952
|
log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -9748,9 +10959,9 @@ async function processAgent(agent, agentStates) {
|
|
|
9748
10959
|
}
|
|
9749
10960
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
|
|
9750
10961
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
9751
|
-
if (mcpPath &&
|
|
10962
|
+
if (mcpPath && existsSync11(mcpPath)) {
|
|
9752
10963
|
try {
|
|
9753
|
-
const cfg = JSON.parse(
|
|
10964
|
+
const cfg = JSON.parse(readFileSync17(mcpPath, "utf-8"));
|
|
9754
10965
|
const expectedRemoteKeys = new Set(
|
|
9755
10966
|
integrations.map((i) => i.definition_id)
|
|
9756
10967
|
);
|
|
@@ -9775,11 +10986,11 @@ async function processAgent(agent, agentStates) {
|
|
|
9775
10986
|
recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
|
|
9776
10987
|
}
|
|
9777
10988
|
if (intHash !== prevIntHash) {
|
|
9778
|
-
const projectDir =
|
|
9779
|
-
const envIntPath =
|
|
10989
|
+
const projectDir = join20(homedir11(), ".augmented", agent.code_name, "project");
|
|
10990
|
+
const envIntPath = join20(projectDir, ".env.integrations");
|
|
9780
10991
|
let preWriteEnv;
|
|
9781
10992
|
try {
|
|
9782
|
-
preWriteEnv =
|
|
10993
|
+
preWriteEnv = readFileSync17(envIntPath, "utf-8");
|
|
9783
10994
|
} catch {
|
|
9784
10995
|
preWriteEnv = void 0;
|
|
9785
10996
|
}
|
|
@@ -9791,9 +11002,9 @@ async function processAgent(agent, agentStates) {
|
|
|
9791
11002
|
let rotationHandled = true;
|
|
9792
11003
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
9793
11004
|
try {
|
|
9794
|
-
const projectMcpPath =
|
|
9795
|
-
const postWriteEnv =
|
|
9796
|
-
const mcpContent =
|
|
11005
|
+
const projectMcpPath = join20(projectDir, ".mcp.json");
|
|
11006
|
+
const postWriteEnv = readFileSync17(envIntPath, "utf-8");
|
|
11007
|
+
const mcpContent = readFileSync17(projectMcpPath, "utf-8");
|
|
9797
11008
|
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
9798
11009
|
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
9799
11010
|
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
@@ -9809,7 +11020,8 @@ async function processAgent(agent, agentStates) {
|
|
|
9809
11020
|
isolated: isolationMode(agent.code_name) === "docker"
|
|
9810
11021
|
});
|
|
9811
11022
|
}
|
|
9812
|
-
const
|
|
11023
|
+
const liveProxyVars = liveProxyExtraHeaderVars(mcpJsonForReap);
|
|
11024
|
+
const respawnVars = envOnlyRespawnVars(changedVars, mcpJsonForReap, CHANNEL_SECRET_ENV_KEYS).filter((v) => !liveProxyVars.has(v));
|
|
9813
11025
|
const envDiff = classifyEnvIntegrationsDiff(preWriteEnv, postWriteEnv);
|
|
9814
11026
|
const rotatedOnlyVars = new Set(envDiff.rotated);
|
|
9815
11027
|
const envRespawnReason = respawnVars.length > 0 && respawnVars.every((v) => rotatedOnlyVars.has(v)) ? "credential-rotation" : "hot-reload-mcp";
|
|
@@ -9869,10 +11081,10 @@ async function processAgent(agent, agentStates) {
|
|
|
9869
11081
|
desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
|
|
9870
11082
|
}
|
|
9871
11083
|
const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
|
|
9872
|
-
const headersHash =
|
|
11084
|
+
const headersHash = createHash12("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
|
|
9873
11085
|
return `${e.serverId}|${e.url}|${headersHash}`;
|
|
9874
11086
|
}).join("\n");
|
|
9875
|
-
const mcpHash =
|
|
11087
|
+
const mcpHash = createHash12("sha256").update(hashBasis).digest("hex").slice(0, 16);
|
|
9876
11088
|
const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
|
|
9877
11089
|
const structureHash = managedMcpStructureHash(desiredEntries);
|
|
9878
11090
|
const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
|
|
@@ -9887,15 +11099,15 @@ async function processAgent(agent, agentStates) {
|
|
|
9887
11099
|
if (mcpHash !== prevMcpHash) {
|
|
9888
11100
|
for (const e of desiredEntries) {
|
|
9889
11101
|
frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
|
|
9890
|
-
const urlHash =
|
|
11102
|
+
const urlHash = createHash12("sha256").update(e.url).digest("hex").slice(0, 12);
|
|
9891
11103
|
log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
|
|
9892
11104
|
}
|
|
9893
11105
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
|
|
9894
11106
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
9895
11107
|
if (mcpPath) {
|
|
9896
11108
|
try {
|
|
9897
|
-
const { readFileSync:
|
|
9898
|
-
const mcpConfig = JSON.parse(
|
|
11109
|
+
const { readFileSync: readFileSync18 } = await import("fs");
|
|
11110
|
+
const mcpConfig = JSON.parse(readFileSync18(mcpPath, "utf-8"));
|
|
9899
11111
|
if (mcpConfig.mcpServers) {
|
|
9900
11112
|
for (const key of Object.keys(mcpConfig.mcpServers)) {
|
|
9901
11113
|
if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
|
|
@@ -9996,7 +11208,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9996
11208
|
if (frameworkAdapter.installSkillFiles) {
|
|
9997
11209
|
const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
|
|
9998
11210
|
const installedIntegrationSkills = [];
|
|
9999
|
-
const { createHash:
|
|
11211
|
+
const { createHash: createHash13 } = await import("crypto");
|
|
10000
11212
|
const refreshAny = refreshData;
|
|
10001
11213
|
const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
|
|
10002
11214
|
const contextBySlug = /* @__PURE__ */ new Map();
|
|
@@ -10025,7 +11237,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10025
11237
|
)
|
|
10026
11238
|
}));
|
|
10027
11239
|
const bundle = buildIntegrationBundle(renderedScopes);
|
|
10028
|
-
const contentHash =
|
|
11240
|
+
const contentHash = createHash13("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
10029
11241
|
const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
|
|
10030
11242
|
if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
10031
11243
|
frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
|
|
@@ -10038,18 +11250,18 @@ async function processAgent(agent, agentStates) {
|
|
|
10038
11250
|
}
|
|
10039
11251
|
try {
|
|
10040
11252
|
const { readdirSync: readdirSync6, rmSync: rmSync5 } = await import("fs");
|
|
10041
|
-
const { homedir:
|
|
11253
|
+
const { homedir: homedir12 } = await import("os");
|
|
10042
11254
|
const frameworkId2 = frameworkAdapter.id;
|
|
10043
11255
|
const candidateSkillDirs = [
|
|
10044
11256
|
// Claude Code — framework runtime tree
|
|
10045
|
-
|
|
11257
|
+
join20(homedir12(), ".augmented", agent.code_name, "skills"),
|
|
10046
11258
|
// Claude Code — project tree
|
|
10047
|
-
|
|
11259
|
+
join20(homedir12(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
10048
11260
|
// Defensive: legacy provision-side path, not currently an
|
|
10049
11261
|
// install target but cheap to sweep.
|
|
10050
|
-
|
|
11262
|
+
join20(agentDir, ".claude", "skills")
|
|
10051
11263
|
];
|
|
10052
|
-
const existingDirs = candidateSkillDirs.filter((d) =>
|
|
11264
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync11(d));
|
|
10053
11265
|
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
10054
11266
|
for (const dir of existingDirs) {
|
|
10055
11267
|
try {
|
|
@@ -10063,8 +11275,8 @@ async function processAgent(agent, agentStates) {
|
|
|
10063
11275
|
}
|
|
10064
11276
|
const removeSkillFolder = (entry, reason) => {
|
|
10065
11277
|
for (const dir of existingDirs) {
|
|
10066
|
-
const p =
|
|
10067
|
-
if (
|
|
11278
|
+
const p = join20(dir, entry);
|
|
11279
|
+
if (existsSync11(p)) {
|
|
10068
11280
|
rmSync5(p, { recursive: true, force: true });
|
|
10069
11281
|
}
|
|
10070
11282
|
}
|
|
@@ -10083,7 +11295,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10083
11295
|
const sharedSkillsPayload = refreshAny.shared_skills;
|
|
10084
11296
|
const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
|
|
10085
11297
|
const manifestPath = managedSkillManifestPath(
|
|
10086
|
-
|
|
11298
|
+
join20(homedir11(), ".augmented", agent.code_name)
|
|
10087
11299
|
);
|
|
10088
11300
|
const prevIds = /* @__PURE__ */ new Set([
|
|
10089
11301
|
...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
|
|
@@ -10092,7 +11304,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10092
11304
|
const plan = planGlobalSkillSync(
|
|
10093
11305
|
[...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
|
|
10094
11306
|
prevIds,
|
|
10095
|
-
(content) =>
|
|
11307
|
+
(content) => createHash13("sha256").update(content).digest("hex").slice(0, 12),
|
|
10096
11308
|
(skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
|
|
10097
11309
|
{ desiredResolved }
|
|
10098
11310
|
);
|
|
@@ -10103,15 +11315,15 @@ async function processAgent(agent, agentStates) {
|
|
|
10103
11315
|
}
|
|
10104
11316
|
if (plan.removes.length) {
|
|
10105
11317
|
const globalSkillDirs = [
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
11318
|
+
join20(homedir11(), ".augmented", agent.code_name, "skills"),
|
|
11319
|
+
join20(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
11320
|
+
join20(agentDir, ".claude", "skills")
|
|
10109
11321
|
];
|
|
10110
11322
|
for (const id of plan.removes) {
|
|
10111
11323
|
let prunedAny = false;
|
|
10112
11324
|
for (const dir of globalSkillDirs) {
|
|
10113
|
-
const p =
|
|
10114
|
-
if (
|
|
11325
|
+
const p = join20(dir, id);
|
|
11326
|
+
if (existsSync11(p) && existsSync11(join20(p, "SKILL.md"))) {
|
|
10115
11327
|
rmSync4(p, { recursive: true, force: true });
|
|
10116
11328
|
prunedAny = true;
|
|
10117
11329
|
}
|
|
@@ -10143,7 +11355,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10143
11355
|
const slug = hook.integration_slug ?? hook.plugin_slug;
|
|
10144
11356
|
if (!slug) continue;
|
|
10145
11357
|
try {
|
|
10146
|
-
const scriptHash =
|
|
11358
|
+
const scriptHash = createHash13("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
10147
11359
|
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
|
|
10148
11360
|
if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
10149
11361
|
const result = await frameworkAdapter.executePluginHook({
|
|
@@ -10158,9 +11370,9 @@ async function processAgent(agent, agentStates) {
|
|
|
10158
11370
|
} else if (result.timedOut) {
|
|
10159
11371
|
log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
10160
11372
|
} else {
|
|
10161
|
-
const stderrHash =
|
|
11373
|
+
const stderrHash = createHash13("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
10162
11374
|
const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
|
|
10163
|
-
const missingCmdHash = missingCmd ?
|
|
11375
|
+
const missingCmdHash = missingCmd ? createHash13("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
|
|
10164
11376
|
log(
|
|
10165
11377
|
`Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
|
|
10166
11378
|
);
|
|
@@ -10314,8 +11526,8 @@ async function processAgent(agent, agentStates) {
|
|
|
10314
11526
|
const sess = getSessionState(agent.code_name);
|
|
10315
11527
|
let mcpJsonParsed = null;
|
|
10316
11528
|
try {
|
|
10317
|
-
const mcpPath =
|
|
10318
|
-
mcpJsonParsed = JSON.parse(
|
|
11529
|
+
const mcpPath = join20(getProjectDir(agent.code_name), ".mcp.json");
|
|
11530
|
+
mcpJsonParsed = JSON.parse(readFileSync17(mcpPath, "utf-8"));
|
|
10319
11531
|
} catch {
|
|
10320
11532
|
}
|
|
10321
11533
|
reapMissingMcpSessions({
|
|
@@ -10640,10 +11852,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
10640
11852
|
}
|
|
10641
11853
|
}
|
|
10642
11854
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
10643
|
-
if (trackedFiles.length > 0 &&
|
|
11855
|
+
if (trackedFiles.length > 0 && existsSync11(agentDir)) {
|
|
10644
11856
|
const hashes = /* @__PURE__ */ new Map();
|
|
10645
11857
|
for (const file of trackedFiles) {
|
|
10646
|
-
const h = hashFile(
|
|
11858
|
+
const h = hashFile(join20(agentDir, file));
|
|
10647
11859
|
if (h) hashes.set(file, h);
|
|
10648
11860
|
}
|
|
10649
11861
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -10658,7 +11870,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
10658
11870
|
refreshData.agent.onboarding_state
|
|
10659
11871
|
);
|
|
10660
11872
|
const obStep = obState.step;
|
|
10661
|
-
const markerPath =
|
|
11873
|
+
const markerPath = join20(homedir11(), ".augmented", agent.code_name, "onboarding-drive.json");
|
|
10662
11874
|
const marker = readOnboardingDriveMarker(markerPath);
|
|
10663
11875
|
const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
|
|
10664
11876
|
if (decision.clearMarker) {
|
|
@@ -10719,11 +11931,55 @@ function deriveEgressAllowlist(toolsRaw) {
|
|
|
10719
11931
|
);
|
|
10720
11932
|
}
|
|
10721
11933
|
var egressAllowlistEqual = (a, b) => a.length === b.length && a.every((d, i) => d === b[i]);
|
|
11934
|
+
async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
|
|
11935
|
+
const codeName = agent.code_name;
|
|
11936
|
+
if (isOpencodeSessionHealthy(codeName)) {
|
|
11937
|
+
await ensureOpencodeSlackIngest({ codeName, agentId: agent.agent_id, log });
|
|
11938
|
+
return { decision: "healthy", spawnAttempted: false, sessionHealthyAfter: true };
|
|
11939
|
+
}
|
|
11940
|
+
stopOpencodeSlackIngest(codeName, log);
|
|
11941
|
+
const opencodeProjectDir = join20(getFramework("opencode").getAgentDir(codeName), "project");
|
|
11942
|
+
const openRouterRaw = refreshData.openrouter ?? null;
|
|
11943
|
+
const serveEnv = {
|
|
11944
|
+
AGT_HOST: requireHost(),
|
|
11945
|
+
AGT_API_KEY: getApiKey() ?? void 0,
|
|
11946
|
+
AGT_APP_URL: process.env["AGT_APP_URL"]
|
|
11947
|
+
};
|
|
11948
|
+
if (openRouterRaw?.auth_token) serveEnv["OPENROUTER_API_KEY"] = openRouterRaw.auth_token;
|
|
11949
|
+
const session = await startOpencodeSession({
|
|
11950
|
+
codeName,
|
|
11951
|
+
agentId: agent.agent_id,
|
|
11952
|
+
projectDir: opencodeProjectDir,
|
|
11953
|
+
serveEnv,
|
|
11954
|
+
agentTimezone,
|
|
11955
|
+
// ADR-0047: the opencode container launcher is not built, so the launcher
|
|
11956
|
+
// refuses to spawn under Docker isolation rather than run unisolated on a
|
|
11957
|
+
// multi-tenant host. Single-agent / dedicated hosts (isolation off) run bare.
|
|
11958
|
+
isolated: isolationMode(codeName) === "docker",
|
|
11959
|
+
log
|
|
11960
|
+
});
|
|
11961
|
+
agentState.persistentSessionAgents.add(codeName);
|
|
11962
|
+
if (session.status === "crashed") {
|
|
11963
|
+
const tail = session.lastFailureTail ?? readOpencodePaneLogTail(codeName);
|
|
11964
|
+
return {
|
|
11965
|
+
decision: "spawn",
|
|
11966
|
+
spawnAttempted: true,
|
|
11967
|
+
sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
|
|
11968
|
+
detail: tail ? `opencode spawn failed: ${tail.slice(0, 200)}` : "opencode spawn failed"
|
|
11969
|
+
};
|
|
11970
|
+
}
|
|
11971
|
+
return {
|
|
11972
|
+
decision: "spawn",
|
|
11973
|
+
spawnAttempted: true,
|
|
11974
|
+
sessionHealthyAfter: isOpencodeSessionHealthy(codeName),
|
|
11975
|
+
detail: `opencode serve on 127.0.0.1:${session.port ?? "?"}`
|
|
11976
|
+
};
|
|
11977
|
+
}
|
|
10722
11978
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
10723
11979
|
const codeName = agent.code_name;
|
|
10724
11980
|
const projectDir = getProjectDir(codeName);
|
|
10725
|
-
const mcpConfigPath =
|
|
10726
|
-
const claudeMdPath =
|
|
11981
|
+
const mcpConfigPath = join20(projectDir, ".mcp.json");
|
|
11982
|
+
const claudeMdPath = join20(projectDir, "CLAUDE.md");
|
|
10727
11983
|
if (restartBreaker.isTripped(codeName)) {
|
|
10728
11984
|
const trip = restartBreaker.getTrip(codeName);
|
|
10729
11985
|
return {
|
|
@@ -10745,6 +12001,10 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
10745
12001
|
}
|
|
10746
12002
|
return resolveAgentTimezone(ownTz, teamTz, orgTierTz);
|
|
10747
12003
|
})();
|
|
12004
|
+
const frameworkId = agentFrameworkCache.get(codeName) ?? DEFAULT_FRAMEWORK;
|
|
12005
|
+
if (frameworkId === "opencode") {
|
|
12006
|
+
return ensureOpencodeRuntime(agent, refreshData, agentTimezone ?? null);
|
|
12007
|
+
}
|
|
10748
12008
|
const channelConfigs = refreshData.channel_configs;
|
|
10749
12009
|
const { devChannels, pluginChannels } = resolveChannelLaunchFlags(
|
|
10750
12010
|
channelConfigs,
|
|
@@ -10877,7 +12137,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
10877
12137
|
const ctx = getLastFailureContext(codeName);
|
|
10878
12138
|
const recovery = prepareForRespawn(codeName);
|
|
10879
12139
|
const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
|
|
10880
|
-
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${
|
|
12140
|
+
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash12("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
|
|
10881
12141
|
const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
|
|
10882
12142
|
const recoverySummary = recovery ? `; recovery=${recovery}` : "";
|
|
10883
12143
|
log(
|
|
@@ -10891,7 +12151,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
|
|
|
10891
12151
|
);
|
|
10892
12152
|
getHostId().then((hostId) => {
|
|
10893
12153
|
if (!hostId) return;
|
|
10894
|
-
const paneTailHash = zombie.paneTail ? `sha256:${
|
|
12154
|
+
const paneTailHash = zombie.paneTail ? `sha256:${createHash12("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
|
|
10895
12155
|
return api.post("/host/events", {
|
|
10896
12156
|
host_id: hostId,
|
|
10897
12157
|
agent_code_name: codeName,
|
|
@@ -11040,7 +12300,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash11("sha256")
|
|
|
11040
12300
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
11041
12301
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
11042
12302
|
}
|
|
11043
|
-
const stableTasksHash =
|
|
12303
|
+
const stableTasksHash = createHash12("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
11044
12304
|
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
11045
12305
|
if (stableTasksHash !== prevHash) {
|
|
11046
12306
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
@@ -11257,7 +12517,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
11257
12517
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
11258
12518
|
void (async () => {
|
|
11259
12519
|
try {
|
|
11260
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
12520
|
+
const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
|
|
11261
12521
|
await api.post("/host/heartbeat", {
|
|
11262
12522
|
host_id: hostId,
|
|
11263
12523
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -11307,7 +12567,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
11307
12567
|
}
|
|
11308
12568
|
try {
|
|
11309
12569
|
const hostId = await getHostId();
|
|
11310
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
12570
|
+
const { collectDiagnostics } = await import("../persistent-session-ZQSAZIVM.js");
|
|
11311
12571
|
await api.post("/host/heartbeat", {
|
|
11312
12572
|
host_id: hostId,
|
|
11313
12573
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -11528,6 +12788,46 @@ async function pollDirectChatMessages(agentStates) {
|
|
|
11528
12788
|
}
|
|
11529
12789
|
}
|
|
11530
12790
|
}
|
|
12791
|
+
async function processDirectChatMessageOpencode(agent, msg) {
|
|
12792
|
+
if (!isOpencodeSessionHealthy(agent.codeName)) {
|
|
12793
|
+
log(`[direct-chat] opencode server not running for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
|
|
12794
|
+
return;
|
|
12795
|
+
}
|
|
12796
|
+
let result;
|
|
12797
|
+
try {
|
|
12798
|
+
result = await injectOpencodeMessage(agent.codeName, {
|
|
12799
|
+
channelId: "direct-chat",
|
|
12800
|
+
conversationKey: msg.session_id,
|
|
12801
|
+
senderId: "webapp",
|
|
12802
|
+
text: msg.content,
|
|
12803
|
+
// Provenance parity with the Claude Code <channel> envelope: source +
|
|
12804
|
+
// reply-expected, folded into the prompt by the bridge.
|
|
12805
|
+
meta: { source: "direct-chat", user: "webapp", lane: "conversational", requires_reply: "true" }
|
|
12806
|
+
});
|
|
12807
|
+
} catch (err) {
|
|
12808
|
+
log(`[direct-chat] opencode inject failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
|
|
12809
|
+
return;
|
|
12810
|
+
}
|
|
12811
|
+
if (result.status === "replied" && result.reply) {
|
|
12812
|
+
try {
|
|
12813
|
+
await api.post("/host/direct-chat/reply", {
|
|
12814
|
+
agent_id: agent.agentId,
|
|
12815
|
+
session_id: msg.session_id,
|
|
12816
|
+
content: result.reply,
|
|
12817
|
+
message_ids: [msg.id]
|
|
12818
|
+
});
|
|
12819
|
+
log(`[direct-chat] opencode replied for '${agent.codeName}' (msg=${msg.id}, ${result.reply.length} chars)`);
|
|
12820
|
+
} catch (err) {
|
|
12821
|
+
log(`[direct-chat] opencode reply POST failed for '${agent.codeName}' (msg=${msg.id}): ${err.message} \u2014 leaving for redelivery`);
|
|
12822
|
+
}
|
|
12823
|
+
return;
|
|
12824
|
+
}
|
|
12825
|
+
if (result.status === "declined") {
|
|
12826
|
+
log(`[direct-chat] opencode gate declined message for '${agent.codeName}' (msg=${msg.id}): ${result.reason}`);
|
|
12827
|
+
return;
|
|
12828
|
+
}
|
|
12829
|
+
log(`[direct-chat] opencode produced no reply for '${agent.codeName}' (msg=${msg.id}) \u2014 leaving for redelivery`);
|
|
12830
|
+
}
|
|
11531
12831
|
async function processDirectChatMessage(agent, msg) {
|
|
11532
12832
|
noteInbound(agent.codeName);
|
|
11533
12833
|
const fw = agentFrameworkCache.get(agent.codeName) ?? DEFAULT_FRAMEWORK;
|
|
@@ -11544,12 +12844,16 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
11544
12844
|
}
|
|
11545
12845
|
return;
|
|
11546
12846
|
}
|
|
12847
|
+
if (fw === "opencode") {
|
|
12848
|
+
await processDirectChatMessageOpencode(agent, msg);
|
|
12849
|
+
return;
|
|
12850
|
+
}
|
|
11547
12851
|
if (isSessionHealthy(agent.codeName)) {
|
|
11548
12852
|
const useDoorbell = hostFlagStore().getBoolean("direct-chat-doorbell") || isolationMode(agent.codeName) === "docker";
|
|
11549
12853
|
if (useDoorbell) {
|
|
11550
12854
|
try {
|
|
11551
|
-
const doorbell = directChatDoorbellPath(agent.agentId,
|
|
11552
|
-
|
|
12855
|
+
const doorbell = directChatDoorbellPath(agent.agentId, homedir11());
|
|
12856
|
+
mkdirSync8(dirname6(doorbell), { recursive: true });
|
|
11553
12857
|
writeFileSync8(doorbell, String(Date.now()));
|
|
11554
12858
|
log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
|
|
11555
12859
|
return;
|
|
@@ -11637,8 +12941,8 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
|
|
|
11637
12941
|
}
|
|
11638
12942
|
if (run_id) openInjectedRunByCode.set(codeName, run_id);
|
|
11639
12943
|
try {
|
|
11640
|
-
const doorbell = directChatDoorbellPath(agentId,
|
|
11641
|
-
|
|
12944
|
+
const doorbell = directChatDoorbellPath(agentId, homedir11());
|
|
12945
|
+
mkdirSync8(dirname6(doorbell), { recursive: true });
|
|
11642
12946
|
writeFileSync8(doorbell, String(Date.now()));
|
|
11643
12947
|
} catch (err) {
|
|
11644
12948
|
log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
|
|
@@ -11731,7 +13035,7 @@ async function processClaudePairSessions(agents) {
|
|
|
11731
13035
|
killPairSession,
|
|
11732
13036
|
pairTmuxSession,
|
|
11733
13037
|
finalizeClaudePairOnboarding
|
|
11734
|
-
} = await import("../claude-pair-runtime-
|
|
13038
|
+
} = await import("../claude-pair-runtime-DO6OWWGD.js");
|
|
11735
13039
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
11736
13040
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
11737
13041
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -11766,12 +13070,12 @@ async function processClaudePairSessions(agents) {
|
|
|
11766
13070
|
try {
|
|
11767
13071
|
if (session.status === "initiating") {
|
|
11768
13072
|
log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);
|
|
11769
|
-
const
|
|
11770
|
-
if (!
|
|
13073
|
+
const spawn2 = await spawnPairSession(pairSession);
|
|
13074
|
+
if (!spawn2.ok) {
|
|
11771
13075
|
await reportAndCleanup(session.pair_id, {
|
|
11772
13076
|
status: "failure",
|
|
11773
|
-
error_code:
|
|
11774
|
-
error_message:
|
|
13077
|
+
error_code: spawn2.error.kind,
|
|
13078
|
+
error_message: spawn2.error.kind === "unknown" ? spawn2.error.message : void 0
|
|
11775
13079
|
});
|
|
11776
13080
|
continue;
|
|
11777
13081
|
}
|
|
@@ -11974,8 +13278,8 @@ function parseMemoryFile(raw, fallbackName) {
|
|
|
11974
13278
|
};
|
|
11975
13279
|
}
|
|
11976
13280
|
async function syncMemories(agent, configDir, log2) {
|
|
11977
|
-
const projectDir =
|
|
11978
|
-
const memoryDir =
|
|
13281
|
+
const projectDir = join20(configDir, agent.code_name, "project");
|
|
13282
|
+
const memoryDir = join20(projectDir, "memory");
|
|
11979
13283
|
const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
|
|
11980
13284
|
if (isFreshSync) {
|
|
11981
13285
|
log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
|
|
@@ -11986,15 +13290,15 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11986
13290
|
}
|
|
11987
13291
|
pendingFreshMemorySync.delete(agent.agent_id);
|
|
11988
13292
|
}
|
|
11989
|
-
if (
|
|
13293
|
+
if (existsSync11(memoryDir)) {
|
|
11990
13294
|
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
11991
13295
|
const currentHashes = /* @__PURE__ */ new Map();
|
|
11992
13296
|
const changedMemories = [];
|
|
11993
13297
|
for (const file of readdirSync5(memoryDir)) {
|
|
11994
13298
|
if (!file.endsWith(".md")) continue;
|
|
11995
13299
|
try {
|
|
11996
|
-
const raw =
|
|
11997
|
-
const fileHash =
|
|
13300
|
+
const raw = readFileSync17(join20(memoryDir, file), "utf-8");
|
|
13301
|
+
const fileHash = createHash12("sha256").update(raw).digest("hex").slice(0, 16);
|
|
11998
13302
|
currentHashes.set(file, fileHash);
|
|
11999
13303
|
if (prevHashes.get(file) === fileHash) continue;
|
|
12000
13304
|
const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
|
|
@@ -12018,7 +13322,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
12018
13322
|
} catch (err) {
|
|
12019
13323
|
for (const mem of changedMemories) {
|
|
12020
13324
|
for (const [file] of currentHashes) {
|
|
12021
|
-
const parsed = parseMemoryFile(
|
|
13325
|
+
const parsed = parseMemoryFile(readFileSync17(join20(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
12022
13326
|
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
12023
13327
|
}
|
|
12024
13328
|
}
|
|
@@ -12031,29 +13335,29 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
12031
13335
|
}
|
|
12032
13336
|
}
|
|
12033
13337
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
12034
|
-
const localFiles =
|
|
12035
|
-
const localListHash =
|
|
13338
|
+
const localFiles = existsSync11(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
13339
|
+
const localListHash = createHash12("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
12036
13340
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
12037
13341
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
12038
13342
|
try {
|
|
12039
13343
|
const dbMemories = await api.post("/host/memories", {
|
|
12040
13344
|
agent_id: agent.agent_id
|
|
12041
13345
|
});
|
|
12042
|
-
const responseHash =
|
|
13346
|
+
const responseHash = createHash12("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
|
|
12043
13347
|
if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
|
|
12044
13348
|
return true;
|
|
12045
13349
|
}
|
|
12046
13350
|
lastDownloadHash.set(agent.agent_id, responseHash);
|
|
12047
13351
|
lastLocalFileHash.set(agent.agent_id, localListHash);
|
|
12048
13352
|
if (dbMemories.memories?.length) {
|
|
12049
|
-
|
|
13353
|
+
mkdirSync8(memoryDir, { recursive: true });
|
|
12050
13354
|
let written = 0;
|
|
12051
13355
|
let overwritten = 0;
|
|
12052
13356
|
for (let i = 0; i < dbMemories.memories.length; i++) {
|
|
12053
13357
|
const mem = dbMemories.memories[i];
|
|
12054
13358
|
const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
12055
13359
|
const slug = rawSlug || `memory-${i}`;
|
|
12056
|
-
const filePath =
|
|
13360
|
+
const filePath = join20(memoryDir, `${slug}.md`);
|
|
12057
13361
|
const desired = `---
|
|
12058
13362
|
name: ${JSON.stringify(mem.name)}
|
|
12059
13363
|
type: ${mem.type}
|
|
@@ -12062,10 +13366,10 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
|
12062
13366
|
|
|
12063
13367
|
${mem.content}
|
|
12064
13368
|
`;
|
|
12065
|
-
if (
|
|
13369
|
+
if (existsSync11(filePath)) {
|
|
12066
13370
|
let existing = "";
|
|
12067
13371
|
try {
|
|
12068
|
-
existing =
|
|
13372
|
+
existing = readFileSync17(filePath, "utf-8");
|
|
12069
13373
|
} catch {
|
|
12070
13374
|
}
|
|
12071
13375
|
if (existing === desired) continue;
|
|
@@ -12078,7 +13382,7 @@ ${mem.content}
|
|
|
12078
13382
|
}
|
|
12079
13383
|
if (written > 0 || overwritten > 0) {
|
|
12080
13384
|
const updatedFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
12081
|
-
lastLocalFileHash.set(agent.agent_id,
|
|
13385
|
+
lastLocalFileHash.set(agent.agent_id, createHash12("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
|
|
12082
13386
|
log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
|
|
12083
13387
|
}
|
|
12084
13388
|
}
|
|
@@ -12089,7 +13393,7 @@ ${mem.content}
|
|
|
12089
13393
|
}
|
|
12090
13394
|
}
|
|
12091
13395
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
12092
|
-
if (
|
|
13396
|
+
if (existsSync11(agentDir)) {
|
|
12093
13397
|
try {
|
|
12094
13398
|
rmSync4(agentDir, { recursive: true, force: true });
|
|
12095
13399
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -12113,8 +13417,8 @@ var caffeinateProc = null;
|
|
|
12113
13417
|
async function startCaffeinate() {
|
|
12114
13418
|
if (process.platform !== "darwin") return;
|
|
12115
13419
|
try {
|
|
12116
|
-
const { spawn } = await import("child_process");
|
|
12117
|
-
caffeinateProc =
|
|
13420
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
13421
|
+
caffeinateProc = spawn2("caffeinate", ["-dims"], {
|
|
12118
13422
|
stdio: "ignore",
|
|
12119
13423
|
detached: false
|
|
12120
13424
|
});
|
|
@@ -12261,8 +13565,8 @@ async function killAllAgtTmuxSessions() {
|
|
|
12261
13565
|
["list-sessions", "-F", "#{session_name}"],
|
|
12262
13566
|
{ timeout: 2e3, stdin: "ignore" }
|
|
12263
13567
|
);
|
|
12264
|
-
const
|
|
12265
|
-
for (const session of
|
|
13568
|
+
const sessions2 = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
|
|
13569
|
+
for (const session of sessions2) {
|
|
12266
13570
|
try {
|
|
12267
13571
|
await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
|
|
12268
13572
|
timeout: 2e3,
|
|
@@ -12272,8 +13576,8 @@ async function killAllAgtTmuxSessions() {
|
|
|
12272
13576
|
} catch {
|
|
12273
13577
|
}
|
|
12274
13578
|
}
|
|
12275
|
-
if (
|
|
12276
|
-
log(`Cleaned up ${
|
|
13579
|
+
if (sessions2.length > 0) {
|
|
13580
|
+
log(`Cleaned up ${sessions2.length} agt-* tmux session(s)`);
|
|
12277
13581
|
}
|
|
12278
13582
|
} catch {
|
|
12279
13583
|
}
|
|
@@ -12309,6 +13613,7 @@ async function stopPolling(opts = {}) {
|
|
|
12309
13613
|
const subsysStartedAt = Date.now();
|
|
12310
13614
|
stopCaffeinate();
|
|
12311
13615
|
stopRealtimeChat();
|
|
13616
|
+
stopAllOpencodeSlackIngests(log);
|
|
12312
13617
|
log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
|
|
12313
13618
|
for (const codeName of [...scheduledRunsByCode.keys()]) {
|
|
12314
13619
|
closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
|
|
@@ -12323,8 +13628,8 @@ function startManager(opts) {
|
|
|
12323
13628
|
config = opts;
|
|
12324
13629
|
try {
|
|
12325
13630
|
const stateFile = getStateFile();
|
|
12326
|
-
if (
|
|
12327
|
-
const raw =
|
|
13631
|
+
if (existsSync11(stateFile)) {
|
|
13632
|
+
const raw = readFileSync17(stateFile, "utf-8");
|
|
12328
13633
|
const parsed = JSON.parse(raw);
|
|
12329
13634
|
if (Array.isArray(parsed.agents)) {
|
|
12330
13635
|
state6.agents = parsed.agents;
|
|
@@ -12351,7 +13656,7 @@ function startManager(opts) {
|
|
|
12351
13656
|
log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
|
|
12352
13657
|
}
|
|
12353
13658
|
log(
|
|
12354
|
-
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${
|
|
13659
|
+
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join20(homedir11(), ".augmented", "manager.log")}`
|
|
12355
13660
|
);
|
|
12356
13661
|
deployMcpAssets();
|
|
12357
13662
|
reapOrphanChannelMcps({ log });
|
|
@@ -12372,7 +13677,7 @@ async function reapOrphanedClaudePids() {
|
|
|
12372
13677
|
const looksLikeClaude = (pid) => {
|
|
12373
13678
|
if (process.platform !== "linux") return true;
|
|
12374
13679
|
try {
|
|
12375
|
-
const comm =
|
|
13680
|
+
const comm = readFileSync17(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
|
|
12376
13681
|
return comm.includes("claude");
|
|
12377
13682
|
} catch {
|
|
12378
13683
|
return false;
|
|
@@ -12390,16 +13695,16 @@ async function reapOrphanedClaudePids() {
|
|
|
12390
13695
|
},
|
|
12391
13696
|
looksLikeClaude
|
|
12392
13697
|
);
|
|
12393
|
-
for (const
|
|
13698
|
+
for (const spawn2 of decision.toKill) {
|
|
12394
13699
|
try {
|
|
12395
|
-
process.kill(
|
|
13700
|
+
process.kill(spawn2.pid, "SIGKILL");
|
|
12396
13701
|
} catch (err) {
|
|
12397
|
-
log(`[drain] reaper SIGKILL pid=${
|
|
13702
|
+
log(`[drain] reaper SIGKILL pid=${spawn2.pid} failed: ${err.message}`);
|
|
12398
13703
|
}
|
|
12399
13704
|
}
|
|
12400
|
-
for (const
|
|
13705
|
+
for (const spawn2 of decision.pidReusedSkipped) {
|
|
12401
13706
|
log(
|
|
12402
|
-
`[drain] reaper skipped pid=${
|
|
13707
|
+
`[drain] reaper skipped pid=${spawn2.pid} \u2014 PID reuse suspected (identity probe rejected)`
|
|
12403
13708
|
);
|
|
12404
13709
|
}
|
|
12405
13710
|
log(formatReaperBootLine({
|
|
@@ -12469,14 +13774,14 @@ function restartRunningChannelMcps(basenames) {
|
|
|
12469
13774
|
}
|
|
12470
13775
|
}
|
|
12471
13776
|
function deployMcpAssets() {
|
|
12472
|
-
const targetDir =
|
|
12473
|
-
|
|
13777
|
+
const targetDir = join20(homedir11(), ".augmented", "_mcp");
|
|
13778
|
+
mkdirSync8(targetDir, { recursive: true });
|
|
12474
13779
|
const moduleDir = dirname6(fileURLToPath(import.meta.url));
|
|
12475
13780
|
let mcpSourceDir = "";
|
|
12476
13781
|
let dir = moduleDir;
|
|
12477
13782
|
for (let i = 0; i < 6; i++) {
|
|
12478
|
-
const candidate =
|
|
12479
|
-
if (
|
|
13783
|
+
const candidate = join20(dir, "dist", "mcp");
|
|
13784
|
+
if (existsSync11(join20(candidate, "index.js"))) {
|
|
12480
13785
|
mcpSourceDir = candidate;
|
|
12481
13786
|
break;
|
|
12482
13787
|
}
|
|
@@ -12491,8 +13796,8 @@ function deployMcpAssets() {
|
|
|
12491
13796
|
const changedBasenames = [];
|
|
12492
13797
|
const fileHash = (p) => {
|
|
12493
13798
|
try {
|
|
12494
|
-
if (!
|
|
12495
|
-
return
|
|
13799
|
+
if (!existsSync11(p)) return null;
|
|
13800
|
+
return createHash12("sha256").update(readFileSync17(p)).digest("hex");
|
|
12496
13801
|
} catch {
|
|
12497
13802
|
return null;
|
|
12498
13803
|
}
|
|
@@ -12556,9 +13861,9 @@ function deployMcpAssets() {
|
|
|
12556
13861
|
// needs restarting to pick up a token rotation.
|
|
12557
13862
|
"xero.js"
|
|
12558
13863
|
]) {
|
|
12559
|
-
const src =
|
|
12560
|
-
const dst =
|
|
12561
|
-
if (!
|
|
13864
|
+
const src = join20(mcpSourceDir, file);
|
|
13865
|
+
const dst = join20(targetDir, file);
|
|
13866
|
+
if (!existsSync11(src)) continue;
|
|
12562
13867
|
const before = fileHash(dst);
|
|
12563
13868
|
try {
|
|
12564
13869
|
copyFileSync(src, dst);
|
|
@@ -12575,16 +13880,16 @@ function deployMcpAssets() {
|
|
|
12575
13880
|
log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
|
|
12576
13881
|
restartRunningChannelMcps(changedBasenames);
|
|
12577
13882
|
}
|
|
12578
|
-
const localMcpPath =
|
|
13883
|
+
const localMcpPath = join20(targetDir, "index.js");
|
|
12579
13884
|
try {
|
|
12580
|
-
const agentsDir =
|
|
12581
|
-
if (
|
|
13885
|
+
const agentsDir = join20(homedir11(), ".augmented", "agents");
|
|
13886
|
+
if (existsSync11(agentsDir)) {
|
|
12582
13887
|
for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
|
|
12583
13888
|
if (!entry.isDirectory()) continue;
|
|
12584
13889
|
for (const subdir of ["provision", "project"]) {
|
|
12585
|
-
const mcpJsonPath =
|
|
13890
|
+
const mcpJsonPath = join20(agentsDir, entry.name, subdir, ".mcp.json");
|
|
12586
13891
|
try {
|
|
12587
|
-
const raw =
|
|
13892
|
+
const raw = readFileSync17(mcpJsonPath, "utf-8");
|
|
12588
13893
|
if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
|
|
12589
13894
|
const mcpConfig = JSON.parse(raw);
|
|
12590
13895
|
const augServer = mcpConfig.mcpServers?.["augmented"];
|