@integrity-labs/agt-cli 0.28.342 → 0.28.344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +4 -4
- package/dist/{chunk-7OBFGSQS.js → chunk-7MP37HYR.js} +13 -1
- package/dist/{chunk-7OBFGSQS.js.map → chunk-7MP37HYR.js.map} +1 -1
- package/dist/{chunk-V2FVNWF2.js → chunk-AJ23F3MU.js} +19 -4
- package/dist/{chunk-V2FVNWF2.js.map → chunk-AJ23F3MU.js.map} +1 -1
- package/dist/{chunk-I3YS5WFV.js → chunk-EP6E6CIY.js} +3 -3
- package/dist/chunk-EP6E6CIY.js.map +1 -0
- package/dist/{claude-pair-runtime-NUZWSILK.js → claude-pair-runtime-JU3CVFUU.js} +2 -2
- package/dist/lib/manager-worker.js +988 -187
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/origami.js +12 -0
- package/dist/{persistent-session-XWJSAZB4.js → persistent-session-IDWQO7QL.js} +2 -2
- package/dist/{responsiveness-probe-XLS2FXIX.js → responsiveness-probe-WF3SMLS6.js} +2 -2
- package/dist/{claude-scheduler-FATCLHDM.js → scheduler-engine-HYOOQV4A.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-I3YS5WFV.js.map +0 -1
- /package/dist/{claude-pair-runtime-NUZWSILK.js.map → claude-pair-runtime-JU3CVFUU.js.map} +0 -0
- /package/dist/{claude-scheduler-FATCLHDM.js.map → persistent-session-IDWQO7QL.js.map} +0 -0
- /package/dist/{responsiveness-probe-XLS2FXIX.js.map → responsiveness-probe-WF3SMLS6.js.map} +0 -0
- /package/dist/{persistent-session-XWJSAZB4.js.map → scheduler-engine-HYOOQV4A.js.map} +0 -0
|
@@ -46,14 +46,14 @@ import {
|
|
|
46
46
|
resolveEffectivePinRaw,
|
|
47
47
|
safeWriteJsonAtomic,
|
|
48
48
|
setConfigHash
|
|
49
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-AJ23F3MU.js";
|
|
50
50
|
import {
|
|
51
51
|
getProjectDir as getProjectDir2,
|
|
52
52
|
getReadyTasks,
|
|
53
53
|
loadSchedulerState,
|
|
54
54
|
markTaskFired,
|
|
55
55
|
syncTasksToScheduler
|
|
56
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-EP6E6CIY.js";
|
|
57
57
|
import {
|
|
58
58
|
AnchorSessionClient,
|
|
59
59
|
CONVERSATION_FAILURE_CATEGORIES,
|
|
@@ -135,16 +135,16 @@ import {
|
|
|
135
135
|
takeZombieDetection,
|
|
136
136
|
transcriptActivityAgeSeconds,
|
|
137
137
|
writeEgressAllowlist
|
|
138
|
-
} from "../chunk-
|
|
138
|
+
} from "../chunk-7MP37HYR.js";
|
|
139
139
|
import {
|
|
140
140
|
reapOrphanChannelMcps
|
|
141
141
|
} from "../chunk-XWVM4KPK.js";
|
|
142
142
|
|
|
143
143
|
// src/lib/manager-worker.ts
|
|
144
|
-
import { createHash as
|
|
145
|
-
import { readFileSync as
|
|
144
|
+
import { createHash as createHash14 } from "crypto";
|
|
145
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync13, rmSync as rmSync4, readdirSync as readdirSync5, statSync as statSync4, copyFileSync } from "fs";
|
|
146
146
|
import { execFileSync as syncExecFile } from "child_process";
|
|
147
|
-
import { join as
|
|
147
|
+
import { join as join22, dirname as dirname6 } from "path";
|
|
148
148
|
import { homedir as homedir11 } from "os";
|
|
149
149
|
import { fileURLToPath } from "url";
|
|
150
150
|
|
|
@@ -5608,6 +5608,7 @@ async function spawnServe(config2, session) {
|
|
|
5608
5608
|
mkdirSync6(join19(homedir9(), ".augmented", codeName), { recursive: true });
|
|
5609
5609
|
const serveEnv = {
|
|
5610
5610
|
...process.env,
|
|
5611
|
+
...readAgentProviderEnv(codeName),
|
|
5611
5612
|
...stripUndefined(config2.serveEnv ?? {}),
|
|
5612
5613
|
OPENCODE_SERVER_PASSWORD: password,
|
|
5613
5614
|
HOME: process.env.HOME?.trim() || homedir9(),
|
|
@@ -5694,6 +5695,29 @@ function stripUndefined(env) {
|
|
|
5694
5695
|
for (const [k, v] of Object.entries(env)) if (v !== void 0) out[k] = v;
|
|
5695
5696
|
return out;
|
|
5696
5697
|
}
|
|
5698
|
+
var PROVIDER_KEY_RE = /^[A-Z][A-Z0-9_]*_API_KEY$/;
|
|
5699
|
+
function readAgentProviderEnv(codeName, dir) {
|
|
5700
|
+
const file = join19(dir ?? join19(homedir9(), ".augmented", codeName), ".env");
|
|
5701
|
+
const out = {};
|
|
5702
|
+
try {
|
|
5703
|
+
if (!existsSync10(file)) return out;
|
|
5704
|
+
for (const raw of readFileSync16(file, "utf-8").split("\n")) {
|
|
5705
|
+
const line = raw.trim();
|
|
5706
|
+
if (!line || line.startsWith("#")) continue;
|
|
5707
|
+
const eq = line.indexOf("=");
|
|
5708
|
+
if (eq <= 0) continue;
|
|
5709
|
+
const key = line.slice(0, eq).trim();
|
|
5710
|
+
if (!PROVIDER_KEY_RE.test(key)) continue;
|
|
5711
|
+
let val = line.slice(eq + 1).trim();
|
|
5712
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
5713
|
+
val = val.slice(1, -1);
|
|
5714
|
+
}
|
|
5715
|
+
out[key] = val;
|
|
5716
|
+
}
|
|
5717
|
+
} catch {
|
|
5718
|
+
}
|
|
5719
|
+
return out;
|
|
5720
|
+
}
|
|
5697
5721
|
|
|
5698
5722
|
// src/lib/opencode-slack-ingest.ts
|
|
5699
5723
|
import { createHash as createHash11 } from "crypto";
|
|
@@ -6712,6 +6736,769 @@ function stopAllOpencodeSlackIngests(log2) {
|
|
|
6712
6736
|
for (const codeName of [...ingests.keys()]) stopOpencodeSlackIngest(codeName, log2);
|
|
6713
6737
|
}
|
|
6714
6738
|
|
|
6739
|
+
// src/lib/manager/opencode-scheduler.ts
|
|
6740
|
+
import { createHash as createHash12 } from "crypto";
|
|
6741
|
+
var MAX_OPENCODE_SCHED_CONCURRENCY = 2;
|
|
6742
|
+
var opencodeSchedulerStates = /* @__PURE__ */ new Map();
|
|
6743
|
+
var inFlightOpencodeTasks = /* @__PURE__ */ new Set();
|
|
6744
|
+
var opencodeTaskConcurrency = /* @__PURE__ */ new Map();
|
|
6745
|
+
var knownOpencodeTaskHashes = /* @__PURE__ */ new Map();
|
|
6746
|
+
function shouldDeliver(task, reply) {
|
|
6747
|
+
if ((task.deliveryPolicy ?? "always") === "never") return false;
|
|
6748
|
+
if (task.deliveryMode === "none") return false;
|
|
6749
|
+
if (task.deliveryTo == null) return false;
|
|
6750
|
+
return typeof reply === "string" && reply.trim().length > 0;
|
|
6751
|
+
}
|
|
6752
|
+
async function syncAndCheckOpencodeScheduler(agent, tasks, refreshData) {
|
|
6753
|
+
const codeName = agent.code_name;
|
|
6754
|
+
const tasksHash = createHash12("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
6755
|
+
if (knownOpencodeTaskHashes.get(agent.agent_id) !== tasksHash) {
|
|
6756
|
+
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
6757
|
+
const state8 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
6758
|
+
opencodeSchedulerStates.set(codeName, state8);
|
|
6759
|
+
knownOpencodeTaskHashes.set(agent.agent_id, tasksHash);
|
|
6760
|
+
log(`[opencode-scheduler] tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
6761
|
+
}
|
|
6762
|
+
if (!opencodeSchedulerStates.has(codeName)) {
|
|
6763
|
+
opencodeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
6764
|
+
}
|
|
6765
|
+
const state7 = opencodeSchedulerStates.get(codeName);
|
|
6766
|
+
const ready = getReadyTasks(state7, inFlightOpencodeTasks);
|
|
6767
|
+
if (ready.length === 0) return;
|
|
6768
|
+
if (!isOpencodeSessionHealthy(codeName)) {
|
|
6769
|
+
log(`[opencode-scheduler] '${codeName}' has ${ready.length} due task(s) but no healthy serve yet - deferring`);
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
for (const task of ready) {
|
|
6773
|
+
if ((opencodeTaskConcurrency.get(codeName) ?? 0) >= MAX_OPENCODE_SCHED_CONCURRENCY) break;
|
|
6774
|
+
if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {
|
|
6775
|
+
opencodeSchedulerStates.set(codeName, markTaskFired(codeName, task.taskId, "ok"));
|
|
6776
|
+
continue;
|
|
6777
|
+
}
|
|
6778
|
+
inFlightOpencodeTasks.add(task.taskId);
|
|
6779
|
+
opencodeTaskConcurrency.set(codeName, (opencodeTaskConcurrency.get(codeName) ?? 0) + 1);
|
|
6780
|
+
void fireOpencodeScheduledTask(agent, task).finally(() => {
|
|
6781
|
+
inFlightOpencodeTasks.delete(task.taskId);
|
|
6782
|
+
opencodeTaskConcurrency.set(
|
|
6783
|
+
codeName,
|
|
6784
|
+
Math.max(0, (opencodeTaskConcurrency.get(codeName) ?? 1) - 1)
|
|
6785
|
+
);
|
|
6786
|
+
});
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
async function fireOpencodeScheduledTask(agent, task) {
|
|
6790
|
+
const codeName = agent.code_name;
|
|
6791
|
+
const conversationKey = `scheduled:${task.taskId}:${Date.now()}`;
|
|
6792
|
+
try {
|
|
6793
|
+
const result = await injectOpencodeMessage(
|
|
6794
|
+
codeName,
|
|
6795
|
+
{
|
|
6796
|
+
channelId: "scheduler",
|
|
6797
|
+
conversationKey,
|
|
6798
|
+
senderId: "scheduler",
|
|
6799
|
+
text: task.prompt,
|
|
6800
|
+
meta: {
|
|
6801
|
+
source: "scheduled_task",
|
|
6802
|
+
task: task.name,
|
|
6803
|
+
// The manager delivers the output; the agent isn't replying into a
|
|
6804
|
+
// channel thread itself, so it must not try to.
|
|
6805
|
+
requires_reply: "false"
|
|
6806
|
+
}
|
|
6807
|
+
},
|
|
6808
|
+
{ awaitReply: true }
|
|
6809
|
+
);
|
|
6810
|
+
if (result.status === "declined") {
|
|
6811
|
+
log(`[opencode-scheduler] task '${task.name}' for '${codeName}' declined: ${result.reason}`);
|
|
6812
|
+
return;
|
|
6813
|
+
}
|
|
6814
|
+
const reply = result.status === "replied" ? result.reply : null;
|
|
6815
|
+
if (shouldDeliver(task, reply)) {
|
|
6816
|
+
try {
|
|
6817
|
+
await deliverScheduledTaskOutput(codeName, agent.agent_id, task.deliveryTo, reply, task.taskId);
|
|
6818
|
+
} catch (err) {
|
|
6819
|
+
log(`[opencode-scheduler] delivery failed for task '${task.name}' on '${codeName}': ${err.message}`);
|
|
6820
|
+
}
|
|
6821
|
+
} else if ((task.deliveryPolicy ?? "always") !== "never" && task.deliveryTo != null) {
|
|
6822
|
+
log(`[opencode-scheduler] task '${task.name}' for '${codeName}' produced no output to deliver`);
|
|
6823
|
+
}
|
|
6824
|
+
opencodeSchedulerStates.set(codeName, markTaskFired(codeName, task.taskId, "ok"));
|
|
6825
|
+
} catch (err) {
|
|
6826
|
+
log(`[opencode-scheduler] task '${task.name}' for '${codeName}' failed: ${err.message}`);
|
|
6827
|
+
opencodeSchedulerStates.set(codeName, markTaskFired(codeName, task.taskId, "error"));
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
|
|
6831
|
+
// src/lib/opencode-telegram-ingest.ts
|
|
6832
|
+
import { createHash as createHash13 } from "crypto";
|
|
6833
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync17, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
|
|
6834
|
+
import { randomUUID } from "crypto";
|
|
6835
|
+
import { join as join20 } from "path";
|
|
6836
|
+
|
|
6837
|
+
// src/lib/telegram-ingest.ts
|
|
6838
|
+
import https2 from "https";
|
|
6839
|
+
|
|
6840
|
+
// ../../packages/core/dist/channels/governance/telegram-peer-classifier.js
|
|
6841
|
+
function classifyPeerMessage2(msg, cfg, self) {
|
|
6842
|
+
if (!msg.from?.is_bot)
|
|
6843
|
+
return { kind: "human" };
|
|
6844
|
+
if (self.bot_id !== null && msg.from.id === self.bot_id) {
|
|
6845
|
+
return { kind: "self" };
|
|
6846
|
+
}
|
|
6847
|
+
if (cfg.peer_disabled_mode === "all") {
|
|
6848
|
+
return { kind: "drop", reason: "peer_disabled_all" };
|
|
6849
|
+
}
|
|
6850
|
+
if (cfg.peer_agent_mode === "off") {
|
|
6851
|
+
return { kind: "drop", reason: "mode_off" };
|
|
6852
|
+
}
|
|
6853
|
+
const chatId = String(msg.chat.id);
|
|
6854
|
+
if (cfg.peer_group_ids.length === 0) {
|
|
6855
|
+
if (msg.chat.type !== "private") {
|
|
6856
|
+
return { kind: "drop", reason: "chat_not_allowed" };
|
|
6857
|
+
}
|
|
6858
|
+
} else if (!cfg.peer_group_ids.includes(chatId)) {
|
|
6859
|
+
return { kind: "drop", reason: "chat_not_allowed" };
|
|
6860
|
+
}
|
|
6861
|
+
const peer = cfg.peers.find((p) => p.bot_id === msg.from.id);
|
|
6862
|
+
if (!peer) {
|
|
6863
|
+
return { kind: "drop", reason: "unknown_peer" };
|
|
6864
|
+
}
|
|
6865
|
+
if (peer.gate_path === null) {
|
|
6866
|
+
return { kind: "drop", reason: "cross_team_grant_missing" };
|
|
6867
|
+
}
|
|
6868
|
+
if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
|
|
6869
|
+
return { kind: "drop", reason: "peer_disabled_cross_team" };
|
|
6870
|
+
}
|
|
6871
|
+
if (self.bot_id === null || self.bot_username === null) {
|
|
6872
|
+
return { kind: "drop", reason: "self_resolution_pending" };
|
|
6873
|
+
}
|
|
6874
|
+
const addressing = classifyAddressing2(msg, self.bot_id, self.bot_username);
|
|
6875
|
+
if (!addressing.addressed) {
|
|
6876
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
6877
|
+
}
|
|
6878
|
+
if (addressing.viaReplyOnly) {
|
|
6879
|
+
const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(chatId);
|
|
6880
|
+
const respondMode = cfg.peer_agent_mode === "respond";
|
|
6881
|
+
if (!(inAllowedGroup && respondMode)) {
|
|
6882
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
return { kind: "peer-ingress", peer };
|
|
6886
|
+
}
|
|
6887
|
+
function classifyAddressing2(msg, ourBotId, ourBotUsername) {
|
|
6888
|
+
let viaMentionOrCommand = false;
|
|
6889
|
+
const text = msg.text ?? msg.caption ?? "";
|
|
6890
|
+
const entities = msg.entities ?? msg.caption_entities ?? [];
|
|
6891
|
+
const usAt = "@" + ourBotUsername;
|
|
6892
|
+
for (const entity of entities) {
|
|
6893
|
+
const slice = text.slice(entity.offset, entity.offset + entity.length).toLowerCase();
|
|
6894
|
+
if (entity.type === "bot_command") {
|
|
6895
|
+
const at = slice.indexOf("@");
|
|
6896
|
+
if (at < 0 || slice.slice(at + 1) === ourBotUsername) {
|
|
6897
|
+
viaMentionOrCommand = true;
|
|
6898
|
+
break;
|
|
6899
|
+
}
|
|
6900
|
+
} else if (entity.type === "mention" && slice === usAt) {
|
|
6901
|
+
viaMentionOrCommand = true;
|
|
6902
|
+
break;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
const viaReply = msg.reply_to_message?.from?.id === ourBotId;
|
|
6906
|
+
if (viaMentionOrCommand)
|
|
6907
|
+
return { addressed: true, viaReplyOnly: false };
|
|
6908
|
+
if (viaReply)
|
|
6909
|
+
return { addressed: true, viaReplyOnly: true };
|
|
6910
|
+
return { addressed: false, viaReplyOnly: false };
|
|
6911
|
+
}
|
|
6912
|
+
var CODE_NAME_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6913
|
+
function parsePeersEnv2(raw, gateRaw) {
|
|
6914
|
+
if (!raw || raw.trim().length === 0)
|
|
6915
|
+
return [];
|
|
6916
|
+
let parsed;
|
|
6917
|
+
try {
|
|
6918
|
+
parsed = JSON.parse(raw);
|
|
6919
|
+
} catch {
|
|
6920
|
+
return [];
|
|
6921
|
+
}
|
|
6922
|
+
if (!Array.isArray(parsed))
|
|
6923
|
+
return [];
|
|
6924
|
+
const gateMap = /* @__PURE__ */ new Map();
|
|
6925
|
+
let gateConfigInvalid = false;
|
|
6926
|
+
const gatePresent = !!(gateRaw && gateRaw.trim().length > 0);
|
|
6927
|
+
if (gatePresent) {
|
|
6928
|
+
try {
|
|
6929
|
+
const gateParsed = JSON.parse(gateRaw);
|
|
6930
|
+
if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
|
|
6931
|
+
gateConfigInvalid = true;
|
|
6932
|
+
} else {
|
|
6933
|
+
for (const [k, v] of Object.entries(gateParsed)) {
|
|
6934
|
+
if (v === null) {
|
|
6935
|
+
gateMap.set(k, null);
|
|
6936
|
+
} else if (typeof v === "string") {
|
|
6937
|
+
const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
|
|
6938
|
+
if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
|
|
6939
|
+
gateMap.set(k, v);
|
|
6940
|
+
} else {
|
|
6941
|
+
gateConfigInvalid = true;
|
|
6942
|
+
break;
|
|
6943
|
+
}
|
|
6944
|
+
} else {
|
|
6945
|
+
gateConfigInvalid = true;
|
|
6946
|
+
break;
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
} catch {
|
|
6951
|
+
gateConfigInvalid = true;
|
|
6952
|
+
}
|
|
6953
|
+
if (gateConfigInvalid) {
|
|
6954
|
+
console.error("[telegram-peer-classifier] TELEGRAM_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)");
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
const out = [];
|
|
6958
|
+
for (const entry of parsed) {
|
|
6959
|
+
if (entry && typeof entry === "object" && typeof entry.code_name === "string" && CODE_NAME_RE2.test(entry.code_name) && typeof entry.agent_id === "string" && typeof entry.bot_id === "number" && Number.isInteger(entry.bot_id) && entry.bot_id > 0) {
|
|
6960
|
+
const e = entry;
|
|
6961
|
+
const peer = { code_name: e.code_name, bot_id: e.bot_id, agent_id: e.agent_id };
|
|
6962
|
+
if (gateConfigInvalid) {
|
|
6963
|
+
peer.gate_path = null;
|
|
6964
|
+
} else {
|
|
6965
|
+
const key = String(e.bot_id);
|
|
6966
|
+
if (gateMap.has(key)) {
|
|
6967
|
+
peer.gate_path = gateMap.get(key) ?? null;
|
|
6968
|
+
} else if (gatePresent) {
|
|
6969
|
+
peer.gate_path = null;
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
out.push(peer);
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6975
|
+
return out;
|
|
6976
|
+
}
|
|
6977
|
+
function parsePeerGroupIdsEnv2(raw) {
|
|
6978
|
+
if (!raw)
|
|
6979
|
+
return [];
|
|
6980
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6981
|
+
}
|
|
6982
|
+
function parsePeerAgentModeEnv2(raw) {
|
|
6983
|
+
if (raw === "listen" || raw === "respond")
|
|
6984
|
+
return raw;
|
|
6985
|
+
return "off";
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6988
|
+
// ../../packages/core/dist/channels/governance/telegram-sender-gate.js
|
|
6989
|
+
function evaluateTelegramInboundGate(evt, deps) {
|
|
6990
|
+
const isBot = Boolean(evt.from?.is_bot);
|
|
6991
|
+
const text = (evt.text ?? evt.caption ?? "").trim();
|
|
6992
|
+
const chatId = String(evt.chat.id);
|
|
6993
|
+
const content = text.length > 0 ? { forward: true } : { forward: false, reason: "empty" };
|
|
6994
|
+
let peerLeaf;
|
|
6995
|
+
let isSelf = false;
|
|
6996
|
+
if (isBot) {
|
|
6997
|
+
const classifierMsg = {
|
|
6998
|
+
message_id: evt.message_id,
|
|
6999
|
+
text: evt.text,
|
|
7000
|
+
caption: evt.caption,
|
|
7001
|
+
from: evt.from ? { id: evt.from.id, username: evt.from.username, is_bot: evt.from.is_bot } : void 0,
|
|
7002
|
+
chat: { id: evt.chat.id, type: evt.chat.type },
|
|
7003
|
+
reply_to_message: evt.reply_to_message ? {
|
|
7004
|
+
message_id: evt.reply_to_message.message_id,
|
|
7005
|
+
from: evt.reply_to_message.from ? { id: evt.reply_to_message.from.id, is_bot: evt.reply_to_message.from.is_bot } : void 0
|
|
7006
|
+
} : void 0,
|
|
7007
|
+
entities: evt.entities,
|
|
7008
|
+
caption_entities: evt.caption_entities
|
|
7009
|
+
};
|
|
7010
|
+
const c = classifyPeerMessage2(classifierMsg, deps.peerConfig, deps.self);
|
|
7011
|
+
isSelf = c.kind === "self";
|
|
7012
|
+
peerLeaf = { kind: c.kind, reason: c.kind === "drop" ? c.reason : void 0 };
|
|
7013
|
+
}
|
|
7014
|
+
const decision = decideInboundAccess({
|
|
7015
|
+
content,
|
|
7016
|
+
// Telegram's only self signal is the peer classifier (Slack catches self at
|
|
7017
|
+
// evt.user===botUserId; Teams inside its activity filter).
|
|
7018
|
+
isSelf,
|
|
7019
|
+
isBot,
|
|
7020
|
+
peer: peerLeaf,
|
|
7021
|
+
// TELEGRAM_ALLOWED_CHATS is Telegram's org-boundary axis. An unlisted chat is
|
|
7022
|
+
// external ⇒ silent drop.
|
|
7023
|
+
orgBoundary: deps.allowedChats.size > 0 && !deps.allowedChats.has(chatId) ? { forward: false } : { forward: true },
|
|
7024
|
+
// No sender-policy mode system and no command dispatch on the injection gate.
|
|
7025
|
+
sameOrg: false
|
|
7026
|
+
});
|
|
7027
|
+
if (decision.kind === "drop")
|
|
7028
|
+
return { admit: false, reason: decision.reason };
|
|
7029
|
+
return { admit: true };
|
|
7030
|
+
}
|
|
7031
|
+
|
|
7032
|
+
// ../../packages/core/dist/channels/governance/telegram-inbound-config.js
|
|
7033
|
+
function parseTelegramAllowedChatsEnv(env) {
|
|
7034
|
+
return new Set((env["TELEGRAM_ALLOWED_CHATS"] ?? "").split(",").map((s) => s.trim()).filter(Boolean));
|
|
7035
|
+
}
|
|
7036
|
+
function parseTelegramPeerDisabledModeEnv(env, onWarn) {
|
|
7037
|
+
const next = (env["PEER_DISABLED"] ?? "").trim().toLowerCase();
|
|
7038
|
+
if (next === "off" || next === "cross_team_only" || next === "all")
|
|
7039
|
+
return next;
|
|
7040
|
+
if (next.length > 0) {
|
|
7041
|
+
onWarn?.(`invalid PEER_DISABLED=${JSON.stringify(env["PEER_DISABLED"])} - defaulting to 'all' (fail-closed)`);
|
|
7042
|
+
return "all";
|
|
7043
|
+
}
|
|
7044
|
+
const legacy = (env["TELEGRAM_PEER_DISABLED"] ?? "").trim().toLowerCase();
|
|
7045
|
+
return legacy === "1" || legacy === "true" || legacy === "yes" ? "all" : "off";
|
|
7046
|
+
}
|
|
7047
|
+
function buildTelegramPeerClassifierConfigFromEnv(env, opts) {
|
|
7048
|
+
return {
|
|
7049
|
+
peer_agent_mode: parsePeerAgentModeEnv2(env["TELEGRAM_PEER_AGENT_MODE"]),
|
|
7050
|
+
peer_group_ids: parsePeerGroupIdsEnv2(env["TELEGRAM_PEER_GROUP_IDS"]),
|
|
7051
|
+
peers: parsePeersEnv2(env["TELEGRAM_PEERS"], env["TELEGRAM_PEERS_GATE"]),
|
|
7052
|
+
peer_disabled_mode: parseTelegramPeerDisabledModeEnv(env, opts?.onWarn)
|
|
7053
|
+
};
|
|
7054
|
+
}
|
|
7055
|
+
|
|
7056
|
+
// src/lib/telegram-ingest.ts
|
|
7057
|
+
var nodeHttpsTelegramFetch = (url, init) => new Promise((resolve, reject) => {
|
|
7058
|
+
const u = new URL(url);
|
|
7059
|
+
const body = init?.body;
|
|
7060
|
+
const headers = { ...init?.headers ?? {} };
|
|
7061
|
+
if (body !== void 0 && headers["Content-Length"] === void 0) {
|
|
7062
|
+
headers["Content-Length"] = String(Buffer.byteLength(body));
|
|
7063
|
+
}
|
|
7064
|
+
const req = https2.request(
|
|
7065
|
+
{
|
|
7066
|
+
hostname: u.hostname,
|
|
7067
|
+
port: 443,
|
|
7068
|
+
path: u.pathname + u.search,
|
|
7069
|
+
method: init?.method ?? "GET",
|
|
7070
|
+
family: 4,
|
|
7071
|
+
// Backstop above the long-poll hold (getUpdates parks up to
|
|
7072
|
+
// LONG_POLL_SECONDS); short calls carry their own AbortSignal.timeout.
|
|
7073
|
+
timeout: POLL_HTTP_TIMEOUT_MS + 5e3,
|
|
7074
|
+
headers
|
|
7075
|
+
},
|
|
7076
|
+
(res) => {
|
|
7077
|
+
let data = "";
|
|
7078
|
+
res.on("data", (d) => {
|
|
7079
|
+
data += d;
|
|
7080
|
+
});
|
|
7081
|
+
res.on("end", () => {
|
|
7082
|
+
const status = res.statusCode ?? 0;
|
|
7083
|
+
resolve({
|
|
7084
|
+
ok: status >= 200 && status < 300,
|
|
7085
|
+
status,
|
|
7086
|
+
json: async () => JSON.parse(data.length > 0 ? data : "{}")
|
|
7087
|
+
});
|
|
7088
|
+
});
|
|
7089
|
+
}
|
|
7090
|
+
);
|
|
7091
|
+
req.on("error", reject);
|
|
7092
|
+
req.on("timeout", () => {
|
|
7093
|
+
req.destroy(new Error("Telegram API socket timeout"));
|
|
7094
|
+
});
|
|
7095
|
+
const signal = init?.signal;
|
|
7096
|
+
if (signal) {
|
|
7097
|
+
if (signal.aborted) req.destroy(new Error("aborted"));
|
|
7098
|
+
else signal.addEventListener("abort", () => req.destroy(new Error("aborted")), { once: true });
|
|
7099
|
+
}
|
|
7100
|
+
if (body !== void 0) req.write(body);
|
|
7101
|
+
req.end();
|
|
7102
|
+
});
|
|
7103
|
+
var TELEGRAM_API = "https://api.telegram.org";
|
|
7104
|
+
var LONG_POLL_SECONDS = 50;
|
|
7105
|
+
var POLL_HTTP_TIMEOUT_MS = (LONG_POLL_SECONDS + 10) * 1e3;
|
|
7106
|
+
var POLL_ERROR_BACKOFF_MS = 5e3;
|
|
7107
|
+
function memoryOffsetStore(initial = 0) {
|
|
7108
|
+
let value = initial;
|
|
7109
|
+
return { load: () => value, save: (o) => {
|
|
7110
|
+
value = o;
|
|
7111
|
+
} };
|
|
7112
|
+
}
|
|
7113
|
+
function resolveTelegramIngestParamsFromEnv(env) {
|
|
7114
|
+
const botToken = env["TELEGRAM_BOT_TOKEN"];
|
|
7115
|
+
if (!botToken) return null;
|
|
7116
|
+
const ackReaction = (env["TELEGRAM_ACK_REACTION"] ?? "").trim() || "\u{1F440}";
|
|
7117
|
+
const skipReaction = (env["TELEGRAM_SKIP_REACTION"] ?? "").trim() || void 0;
|
|
7118
|
+
return {
|
|
7119
|
+
botToken,
|
|
7120
|
+
allowedChats: parseTelegramAllowedChatsEnv(env),
|
|
7121
|
+
peerConfig: buildTelegramPeerClassifierConfigFromEnv(env),
|
|
7122
|
+
ackReaction,
|
|
7123
|
+
...skipReaction ? { skipReaction } : {}
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
7126
|
+
function isTelegramAuthFailure(status, description) {
|
|
7127
|
+
return status === 401 || (description ?? "").toLowerCase().includes("unauthorized");
|
|
7128
|
+
}
|
|
7129
|
+
async function resolveTelegramBotIdentity(botToken, fetchImpl) {
|
|
7130
|
+
const doFetch = fetchImpl ?? nodeHttpsTelegramFetch;
|
|
7131
|
+
const unresolved = { bot_id: null, bot_username: null };
|
|
7132
|
+
try {
|
|
7133
|
+
const res = await doFetch(`${TELEGRAM_API}/bot${botToken}/getMe`, {
|
|
7134
|
+
method: "POST",
|
|
7135
|
+
signal: AbortSignal.timeout(1e4)
|
|
7136
|
+
});
|
|
7137
|
+
const data = await res.json();
|
|
7138
|
+
if (data.ok && data.result) {
|
|
7139
|
+
return {
|
|
7140
|
+
self: {
|
|
7141
|
+
bot_id: data.result.id,
|
|
7142
|
+
// Lowercased to match resolveBotUsername()'s cache convention.
|
|
7143
|
+
bot_username: data.result.username ? data.result.username.toLowerCase() : null
|
|
7144
|
+
},
|
|
7145
|
+
authFailed: false
|
|
7146
|
+
};
|
|
7147
|
+
}
|
|
7148
|
+
return { self: unresolved, authFailed: isTelegramAuthFailure(res.status, data.description) };
|
|
7149
|
+
} catch {
|
|
7150
|
+
return { self: unresolved, authFailed: false };
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
var BoundedSet2 = class {
|
|
7154
|
+
max;
|
|
7155
|
+
set = /* @__PURE__ */ new Set();
|
|
7156
|
+
constructor(max = 500) {
|
|
7157
|
+
this.max = max;
|
|
7158
|
+
}
|
|
7159
|
+
has(k) {
|
|
7160
|
+
return this.set.has(k);
|
|
7161
|
+
}
|
|
7162
|
+
add(k) {
|
|
7163
|
+
this.set.add(k);
|
|
7164
|
+
if (this.set.size > this.max) {
|
|
7165
|
+
const oldest = this.set.values().next().value;
|
|
7166
|
+
if (oldest !== void 0) this.set.delete(oldest);
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
};
|
|
7170
|
+
async function dispatchTelegramInbound(update, ctx) {
|
|
7171
|
+
const msg = update.message;
|
|
7172
|
+
const chatId = String(msg.chat.id);
|
|
7173
|
+
const dedupKey = String(update.update_id);
|
|
7174
|
+
if (ctx.seen.has(dedupKey)) return "dropped_duplicate";
|
|
7175
|
+
ctx.seen.add(dedupKey);
|
|
7176
|
+
const decision = evaluateTelegramInboundGate(msg, {
|
|
7177
|
+
allowedChats: ctx.allowedChats,
|
|
7178
|
+
peerConfig: ctx.peerConfig,
|
|
7179
|
+
self: ctx.self
|
|
7180
|
+
});
|
|
7181
|
+
if (!decision.admit) {
|
|
7182
|
+
ctx.log(`[telegram-ingest:${ctx.codeName}] dropped chat=${redactId(chatId)} reason=${decision.reason}`);
|
|
7183
|
+
return "dropped_gate";
|
|
7184
|
+
}
|
|
7185
|
+
if (ctx.ackReaction) {
|
|
7186
|
+
void ctx.addReaction({ chatId, messageId: msg.message_id, emoji: ctx.ackReaction });
|
|
7187
|
+
}
|
|
7188
|
+
const conversationKey = msg.message_thread_id ? `${chatId}:${msg.message_thread_id}` : chatId;
|
|
7189
|
+
const meta = {
|
|
7190
|
+
source: "telegram",
|
|
7191
|
+
lane: "conversational",
|
|
7192
|
+
requires_reply: "true",
|
|
7193
|
+
chat_id: chatId
|
|
7194
|
+
};
|
|
7195
|
+
if (msg.message_thread_id) meta["message_thread_id"] = String(msg.message_thread_id);
|
|
7196
|
+
const result = await ctx.inject({
|
|
7197
|
+
channelId: "telegram",
|
|
7198
|
+
conversationKey,
|
|
7199
|
+
senderId: msg.from ? String(msg.from.id) : "unknown",
|
|
7200
|
+
text: msg.text ?? msg.caption ?? "",
|
|
7201
|
+
meta
|
|
7202
|
+
});
|
|
7203
|
+
if (result.status === "declined") {
|
|
7204
|
+
ctx.log(`[telegram-ingest:${ctx.codeName}] inject declined reason=${result.reason}`);
|
|
7205
|
+
return "declined";
|
|
7206
|
+
}
|
|
7207
|
+
if (result.status === "replied" && result.reply) {
|
|
7208
|
+
await ctx.sendReply({
|
|
7209
|
+
chatId,
|
|
7210
|
+
text: result.reply,
|
|
7211
|
+
...msg.message_thread_id ? { messageThreadId: msg.message_thread_id } : {}
|
|
7212
|
+
});
|
|
7213
|
+
ctx.log(`[telegram-ingest:${ctx.codeName}] outcome=replied chat=${redactId(chatId)} len=${result.reply.length}`);
|
|
7214
|
+
return "admitted_replied";
|
|
7215
|
+
}
|
|
7216
|
+
ctx.log(
|
|
7217
|
+
`[telegram-ingest:${ctx.codeName}] outcome=no_reply chat=${redactId(chatId)} inject_status=${result.status}`
|
|
7218
|
+
);
|
|
7219
|
+
if (ctx.skipReaction) {
|
|
7220
|
+
void ctx.addReaction({ chatId, messageId: msg.message_id, emoji: ctx.skipReaction });
|
|
7221
|
+
}
|
|
7222
|
+
return "admitted_no_reply";
|
|
7223
|
+
}
|
|
7224
|
+
function redactId(id) {
|
|
7225
|
+
if (!id) return "?";
|
|
7226
|
+
return `${id.slice(0, 2)}***`;
|
|
7227
|
+
}
|
|
7228
|
+
function defaultSendReply2(botToken, fetchImpl, log2) {
|
|
7229
|
+
return async ({ chatId, text, messageThreadId }) => {
|
|
7230
|
+
try {
|
|
7231
|
+
const res = await fetchImpl(`${TELEGRAM_API}/bot${botToken}/sendMessage`, {
|
|
7232
|
+
method: "POST",
|
|
7233
|
+
headers: { "Content-Type": "application/json" },
|
|
7234
|
+
body: JSON.stringify({
|
|
7235
|
+
chat_id: chatId,
|
|
7236
|
+
text,
|
|
7237
|
+
...messageThreadId ? { message_thread_id: messageThreadId } : {}
|
|
7238
|
+
}),
|
|
7239
|
+
// Dedicated short timeout so an outbound reply can't inherit the ~50s
|
|
7240
|
+
// long-poll getUpdates timeout and hang a turn on a slow Telegram edge.
|
|
7241
|
+
signal: AbortSignal.timeout(1e4)
|
|
7242
|
+
});
|
|
7243
|
+
const data = await res.json();
|
|
7244
|
+
if (!data.ok) log2(`[telegram-ingest] sendMessage failed: ${data.description}`);
|
|
7245
|
+
} catch (err) {
|
|
7246
|
+
log2(`[telegram-ingest] sendMessage threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7247
|
+
}
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
function defaultAddReaction2(botToken, fetchImpl, log2) {
|
|
7251
|
+
return async ({ chatId, messageId, emoji }) => {
|
|
7252
|
+
try {
|
|
7253
|
+
const res = await fetchImpl(`${TELEGRAM_API}/bot${botToken}/setMessageReaction`, {
|
|
7254
|
+
method: "POST",
|
|
7255
|
+
headers: { "Content-Type": "application/json" },
|
|
7256
|
+
body: JSON.stringify({
|
|
7257
|
+
chat_id: chatId,
|
|
7258
|
+
message_id: messageId,
|
|
7259
|
+
reaction: [{ type: "emoji", emoji }]
|
|
7260
|
+
}),
|
|
7261
|
+
// Dedicated short timeout — the ack must not inherit the long-poll window.
|
|
7262
|
+
signal: AbortSignal.timeout(1e4)
|
|
7263
|
+
});
|
|
7264
|
+
const data = await res.json();
|
|
7265
|
+
if (!data.ok) log2(`[telegram-ingest] setMessageReaction failed: ${data.description}`);
|
|
7266
|
+
} catch (err) {
|
|
7267
|
+
log2(`[telegram-ingest] setMessageReaction threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7268
|
+
}
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7271
|
+
function sleep(ms) {
|
|
7272
|
+
return new Promise((resolve) => {
|
|
7273
|
+
setTimeout(resolve, ms).unref?.();
|
|
7274
|
+
});
|
|
7275
|
+
}
|
|
7276
|
+
function startTelegramIngest(config2) {
|
|
7277
|
+
const fetchImpl = config2.fetchImpl ?? nodeHttpsTelegramFetch;
|
|
7278
|
+
const sendReply = config2.sendReply ?? defaultSendReply2(config2.botToken, fetchImpl, config2.log);
|
|
7279
|
+
const addReaction = config2.addReaction ?? defaultAddReaction2(config2.botToken, fetchImpl, config2.log);
|
|
7280
|
+
const offsetStore = config2.offsetStore ?? memoryOffsetStore();
|
|
7281
|
+
const seen = new BoundedSet2();
|
|
7282
|
+
const ctx = {
|
|
7283
|
+
codeName: config2.codeName,
|
|
7284
|
+
allowedChats: config2.allowedChats,
|
|
7285
|
+
peerConfig: config2.peerConfig,
|
|
7286
|
+
self: config2.self,
|
|
7287
|
+
inject: config2.inject,
|
|
7288
|
+
sendReply,
|
|
7289
|
+
addReaction,
|
|
7290
|
+
...config2.ackReaction ? { ackReaction: config2.ackReaction } : {},
|
|
7291
|
+
...config2.skipReaction ? { skipReaction: config2.skipReaction } : {},
|
|
7292
|
+
log: config2.log,
|
|
7293
|
+
seen
|
|
7294
|
+
};
|
|
7295
|
+
let stopped = false;
|
|
7296
|
+
let controller = null;
|
|
7297
|
+
let nextOffset = offsetStore.load();
|
|
7298
|
+
const pollLoop = async () => {
|
|
7299
|
+
while (!stopped) {
|
|
7300
|
+
try {
|
|
7301
|
+
controller = new AbortController();
|
|
7302
|
+
const timeout = AbortSignal.timeout(POLL_HTTP_TIMEOUT_MS);
|
|
7303
|
+
const signal = anySignal([controller.signal, timeout]);
|
|
7304
|
+
const res = await fetchImpl(`${TELEGRAM_API}/bot${config2.botToken}/getUpdates`, {
|
|
7305
|
+
method: "POST",
|
|
7306
|
+
headers: { "Content-Type": "application/json" },
|
|
7307
|
+
body: JSON.stringify({
|
|
7308
|
+
offset: nextOffset,
|
|
7309
|
+
timeout: LONG_POLL_SECONDS,
|
|
7310
|
+
allowed_updates: ["message"]
|
|
7311
|
+
}),
|
|
7312
|
+
signal
|
|
7313
|
+
});
|
|
7314
|
+
if (stopped) return;
|
|
7315
|
+
const data = await res.json();
|
|
7316
|
+
if (!data.ok) {
|
|
7317
|
+
config2.log(`[telegram-ingest:${config2.codeName}] getUpdates failed: ${data.description ?? "unknown"}`);
|
|
7318
|
+
await sleep(POLL_ERROR_BACKOFF_MS);
|
|
7319
|
+
continue;
|
|
7320
|
+
}
|
|
7321
|
+
const updates = data.result ?? [];
|
|
7322
|
+
for (const update of updates) {
|
|
7323
|
+
nextOffset = Math.max(nextOffset, update.update_id + 1);
|
|
7324
|
+
const msg = update.message;
|
|
7325
|
+
if (!msg) continue;
|
|
7326
|
+
try {
|
|
7327
|
+
await dispatchTelegramInbound({ update_id: update.update_id, message: msg }, ctx);
|
|
7328
|
+
} catch (err) {
|
|
7329
|
+
config2.log(
|
|
7330
|
+
`[telegram-ingest:${config2.codeName}] dispatch threw: ${err instanceof Error ? err.message : String(err)}`
|
|
7331
|
+
);
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
7334
|
+
if (updates.length > 0) offsetStore.save(nextOffset);
|
|
7335
|
+
} catch (err) {
|
|
7336
|
+
if (stopped) return;
|
|
7337
|
+
config2.log(`[telegram-ingest:${config2.codeName}] poll threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7338
|
+
await sleep(POLL_ERROR_BACKOFF_MS);
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
};
|
|
7342
|
+
void pollLoop();
|
|
7343
|
+
return {
|
|
7344
|
+
stop() {
|
|
7345
|
+
stopped = true;
|
|
7346
|
+
try {
|
|
7347
|
+
controller?.abort();
|
|
7348
|
+
} catch {
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7353
|
+
function anySignal(signals) {
|
|
7354
|
+
const controller = new AbortController();
|
|
7355
|
+
for (const s of signals) {
|
|
7356
|
+
if (s.aborted) {
|
|
7357
|
+
controller.abort();
|
|
7358
|
+
break;
|
|
7359
|
+
}
|
|
7360
|
+
s.addEventListener("abort", () => controller.abort(), { once: true });
|
|
7361
|
+
}
|
|
7362
|
+
return controller.signal;
|
|
7363
|
+
}
|
|
7364
|
+
|
|
7365
|
+
// src/lib/opencode-telegram-ingest.ts
|
|
7366
|
+
var ingests2 = /* @__PURE__ */ new Map();
|
|
7367
|
+
function fingerprint2(params) {
|
|
7368
|
+
return createHash13("sha256").update(
|
|
7369
|
+
JSON.stringify({
|
|
7370
|
+
botToken: params.botToken,
|
|
7371
|
+
allowedChats: [...params.allowedChats].sort(),
|
|
7372
|
+
peerConfig: params.peerConfig,
|
|
7373
|
+
ackReaction: params.ackReaction ?? null,
|
|
7374
|
+
skipReaction: params.skipReaction ?? null
|
|
7375
|
+
})
|
|
7376
|
+
).digest("hex");
|
|
7377
|
+
}
|
|
7378
|
+
function createFileOffsetStore(codeName, log2, currentBotId) {
|
|
7379
|
+
let dir;
|
|
7380
|
+
let filePath;
|
|
7381
|
+
try {
|
|
7382
|
+
dir = getFramework("opencode").getAgentDir(codeName);
|
|
7383
|
+
filePath = join20(dir, "telegram-getupdates-offset-opencode.json");
|
|
7384
|
+
} catch {
|
|
7385
|
+
dir = null;
|
|
7386
|
+
filePath = null;
|
|
7387
|
+
}
|
|
7388
|
+
return {
|
|
7389
|
+
load() {
|
|
7390
|
+
if (!filePath) return 0;
|
|
7391
|
+
try {
|
|
7392
|
+
const parsed = JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
7393
|
+
if (currentBotId != null && typeof parsed?.bot_id === "number" && parsed.bot_id !== currentBotId) {
|
|
7394
|
+
log2(`[telegram-ingest:${codeName}] offset cursor belongs to a different bot; ignoring (bot swap)`);
|
|
7395
|
+
return 0;
|
|
7396
|
+
}
|
|
7397
|
+
const offset = parsed?.offset;
|
|
7398
|
+
if (typeof offset === "number" && Number.isFinite(offset) && offset > 0) {
|
|
7399
|
+
const normalized = Math.floor(offset);
|
|
7400
|
+
if (Number.isSafeInteger(normalized) && normalized > 0) return normalized;
|
|
7401
|
+
}
|
|
7402
|
+
} catch {
|
|
7403
|
+
}
|
|
7404
|
+
return 0;
|
|
7405
|
+
},
|
|
7406
|
+
save(offset) {
|
|
7407
|
+
if (!filePath || !dir) return;
|
|
7408
|
+
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
7409
|
+
try {
|
|
7410
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
7411
|
+
writeFileSync8(
|
|
7412
|
+
tmpPath,
|
|
7413
|
+
JSON.stringify({
|
|
7414
|
+
offset,
|
|
7415
|
+
// Stamp the owning bot so a later token swap invalidates this cursor.
|
|
7416
|
+
...currentBotId != null ? { bot_id: currentBotId } : {},
|
|
7417
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7418
|
+
}),
|
|
7419
|
+
{ mode: 384 }
|
|
7420
|
+
);
|
|
7421
|
+
renameSync2(tmpPath, filePath);
|
|
7422
|
+
} catch (err) {
|
|
7423
|
+
log2(`[telegram-ingest:${codeName}] offset persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7424
|
+
try {
|
|
7425
|
+
if (existsSync11(tmpPath)) unlinkSync(tmpPath);
|
|
7426
|
+
} catch {
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
};
|
|
7431
|
+
}
|
|
7432
|
+
async function ensureOpencodeTelegramIngest(opts) {
|
|
7433
|
+
const { codeName, agentId, log: log2 } = opts;
|
|
7434
|
+
const readEnv = opts.readEnv ?? readChannelServerEnv;
|
|
7435
|
+
let params;
|
|
7436
|
+
try {
|
|
7437
|
+
const env = readEnv(codeName, "telegram");
|
|
7438
|
+
if (!env) {
|
|
7439
|
+
stopOpencodeTelegramIngest(codeName, log2);
|
|
7440
|
+
return;
|
|
7441
|
+
}
|
|
7442
|
+
params = resolveTelegramIngestParamsFromEnv(env);
|
|
7443
|
+
} catch (err) {
|
|
7444
|
+
stopOpencodeTelegramIngest(codeName, log2);
|
|
7445
|
+
log2(`[telegram-ingest:${codeName}] not started: ${err instanceof Error ? err.message : String(err)}`);
|
|
7446
|
+
return;
|
|
7447
|
+
}
|
|
7448
|
+
if (!params) {
|
|
7449
|
+
stopOpencodeTelegramIngest(codeName, log2);
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7452
|
+
const key = fingerprint2(params);
|
|
7453
|
+
const existing = ingests2.get(codeName);
|
|
7454
|
+
if (existing?.key === key && !existing.selfUnresolved) return;
|
|
7455
|
+
const { self, authFailed } = await resolveTelegramBotIdentity(params.botToken, opts.fetchImpl);
|
|
7456
|
+
if (authFailed) {
|
|
7457
|
+
stopOpencodeTelegramIngest(codeName, log2);
|
|
7458
|
+
log2(`[telegram-ingest:${codeName}] not started: Telegram bot token rejected (needs reconnect)`);
|
|
7459
|
+
return;
|
|
7460
|
+
}
|
|
7461
|
+
if (existing?.key === key && self.bot_id === null) return;
|
|
7462
|
+
if (self.bot_id === null) {
|
|
7463
|
+
log2(`[telegram-ingest:${codeName}] bot identity unresolved; peer classification degraded`);
|
|
7464
|
+
}
|
|
7465
|
+
if (existing) {
|
|
7466
|
+
existing.handle.stop();
|
|
7467
|
+
ingests2.delete(codeName);
|
|
7468
|
+
log2(
|
|
7469
|
+
existing.key === key ? `[telegram-ingest:${codeName}] bot identity resolved; restarting ingest` : `[telegram-ingest:${codeName}] config changed; restarting ingest`
|
|
7470
|
+
);
|
|
7471
|
+
}
|
|
7472
|
+
const handle = startTelegramIngest({
|
|
7473
|
+
codeName,
|
|
7474
|
+
agentId,
|
|
7475
|
+
botToken: params.botToken,
|
|
7476
|
+
allowedChats: params.allowedChats,
|
|
7477
|
+
peerConfig: params.peerConfig,
|
|
7478
|
+
self,
|
|
7479
|
+
inject: (msg) => injectOpencodeMessage(codeName, msg, { awaitReply: true }),
|
|
7480
|
+
log: log2,
|
|
7481
|
+
offsetStore: opts.offsetStore ?? createFileOffsetStore(codeName, log2, self.bot_id),
|
|
7482
|
+
...params.ackReaction ? { ackReaction: params.ackReaction } : {},
|
|
7483
|
+
...params.skipReaction ? { skipReaction: params.skipReaction } : {},
|
|
7484
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
7485
|
+
});
|
|
7486
|
+
ingests2.set(codeName, { key, handle, selfUnresolved: self.bot_id === null });
|
|
7487
|
+
log2(
|
|
7488
|
+
`[telegram-ingest:${codeName}] ingest started (peer_mode=${params.peerConfig.peer_agent_mode}, allowlist=${params.allowedChats.size})`
|
|
7489
|
+
);
|
|
7490
|
+
}
|
|
7491
|
+
function stopOpencodeTelegramIngest(codeName, log2) {
|
|
7492
|
+
const entry = ingests2.get(codeName);
|
|
7493
|
+
if (!entry) return;
|
|
7494
|
+
entry.handle.stop();
|
|
7495
|
+
ingests2.delete(codeName);
|
|
7496
|
+
log2?.(`[telegram-ingest:${codeName}] ingest stopped`);
|
|
7497
|
+
}
|
|
7498
|
+
function stopAllOpencodeTelegramIngests(log2) {
|
|
7499
|
+
for (const codeName of [...ingests2.keys()]) stopOpencodeTelegramIngest(codeName, log2);
|
|
7500
|
+
}
|
|
7501
|
+
|
|
6715
7502
|
// src/lib/wedge-detection.ts
|
|
6716
7503
|
var DEFAULTS = {
|
|
6717
7504
|
inboundWaitSeconds: 120,
|
|
@@ -6852,24 +7639,24 @@ function partitionActionableByPoison(actionable, states, config2) {
|
|
|
6852
7639
|
}
|
|
6853
7640
|
|
|
6854
7641
|
// src/lib/restart-flags.ts
|
|
6855
|
-
import { existsSync as
|
|
7642
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync18, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
6856
7643
|
import { homedir as homedir10 } from "os";
|
|
6857
|
-
import { join as
|
|
6858
|
-
import { randomUUID } from "crypto";
|
|
7644
|
+
import { join as join21 } from "path";
|
|
7645
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
6859
7646
|
function restartFlagsDir() {
|
|
6860
|
-
return
|
|
7647
|
+
return join21(homedir10(), ".augmented", "restart-flags");
|
|
6861
7648
|
}
|
|
6862
7649
|
function flagPath(codeName) {
|
|
6863
|
-
return
|
|
7650
|
+
return join21(restartFlagsDir(), `${codeName}.flag`);
|
|
6864
7651
|
}
|
|
6865
7652
|
function readRestartFlags() {
|
|
6866
7653
|
const dir = restartFlagsDir();
|
|
6867
|
-
if (!
|
|
7654
|
+
if (!existsSync12(dir)) return [];
|
|
6868
7655
|
const out = [];
|
|
6869
7656
|
for (const entry of readdirSync4(dir)) {
|
|
6870
7657
|
if (!entry.endsWith(".flag")) continue;
|
|
6871
7658
|
try {
|
|
6872
|
-
const raw =
|
|
7659
|
+
const raw = readFileSync18(join21(dir, entry), "utf8");
|
|
6873
7660
|
const parsed = JSON.parse(raw);
|
|
6874
7661
|
if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
|
|
6875
7662
|
parsed.codeName = entry.replace(/\.flag$/, "");
|
|
@@ -6887,7 +7674,7 @@ function readRestartFlags() {
|
|
|
6887
7674
|
}
|
|
6888
7675
|
function deleteRestartFlag(codeName) {
|
|
6889
7676
|
const path = flagPath(codeName);
|
|
6890
|
-
if (
|
|
7677
|
+
if (existsSync12(path)) {
|
|
6891
7678
|
rmSync3(path, { force: true });
|
|
6892
7679
|
}
|
|
6893
7680
|
}
|
|
@@ -7233,10 +8020,10 @@ function startRealtimeConfig(config2) {
|
|
|
7233
8020
|
filter: filterStr
|
|
7234
8021
|
}, (payload) => {
|
|
7235
8022
|
const agent = payload.new;
|
|
7236
|
-
const
|
|
8023
|
+
const fingerprint3 = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;
|
|
7237
8024
|
const prev = lastKnown.get(agent.agent_id);
|
|
7238
|
-
if (prev ===
|
|
7239
|
-
lastKnown.set(agent.agent_id,
|
|
8025
|
+
if (prev === fingerprint3) return;
|
|
8026
|
+
lastKnown.set(agent.agent_id, fingerprint3);
|
|
7240
8027
|
log2(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);
|
|
7241
8028
|
onConfigChange(agent);
|
|
7242
8029
|
}).subscribe((status) => {
|
|
@@ -8121,15 +8908,15 @@ var runningChannelSecretHashes = /* @__PURE__ */ new Map();
|
|
|
8121
8908
|
var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
|
|
8122
8909
|
function projectMcpHash(_codeName, projectDir) {
|
|
8123
8910
|
try {
|
|
8124
|
-
const raw =
|
|
8125
|
-
return
|
|
8911
|
+
const raw = readFileSync19(join22(projectDir, ".mcp.json"), "utf-8");
|
|
8912
|
+
return createHash14("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
8126
8913
|
} catch {
|
|
8127
8914
|
return null;
|
|
8128
8915
|
}
|
|
8129
8916
|
}
|
|
8130
8917
|
function projectMcpKeys(_codeName, projectDir) {
|
|
8131
8918
|
try {
|
|
8132
|
-
const raw =
|
|
8919
|
+
const raw = readFileSync19(join22(projectDir, ".mcp.json"), "utf-8");
|
|
8133
8920
|
const parsed = JSON.parse(raw);
|
|
8134
8921
|
const servers = parsed.mcpServers;
|
|
8135
8922
|
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
@@ -8147,7 +8934,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
|
|
|
8147
8934
|
else runningMcpServerKeys.delete(codeName);
|
|
8148
8935
|
let launchStructure = null;
|
|
8149
8936
|
try {
|
|
8150
|
-
const raw =
|
|
8937
|
+
const raw = readFileSync19(join22(projectDir, ".mcp.json"), "utf-8");
|
|
8151
8938
|
launchStructure = managedMcpStructureHashFromFile(
|
|
8152
8939
|
JSON.parse(raw),
|
|
8153
8940
|
isManagedMcpServerKey
|
|
@@ -8247,7 +9034,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
|
|
|
8247
9034
|
if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
|
|
8248
9035
|
let mcpJsonForRebind = null;
|
|
8249
9036
|
try {
|
|
8250
|
-
mcpJsonForRebind = JSON.parse(
|
|
9037
|
+
mcpJsonForRebind = JSON.parse(readFileSync19(join22(projectDir, ".mcp.json"), "utf-8"));
|
|
8251
9038
|
} catch {
|
|
8252
9039
|
mcpJsonForRebind = null;
|
|
8253
9040
|
}
|
|
@@ -8376,7 +9163,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
|
8376
9163
|
function projectChannelSecretHash(projectDir) {
|
|
8377
9164
|
try {
|
|
8378
9165
|
const entries = parseEnvIntegrations(
|
|
8379
|
-
|
|
9166
|
+
readFileSync19(join22(projectDir, ".env.integrations"), "utf-8")
|
|
8380
9167
|
);
|
|
8381
9168
|
return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
|
|
8382
9169
|
} catch {
|
|
@@ -8439,6 +9226,7 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
8439
9226
|
const channelCacheMutated = clearAgentState(agentId, codeName);
|
|
8440
9227
|
agentChannelTokens.delete(codeName);
|
|
8441
9228
|
stopOpencodeSlackIngest(codeName, log);
|
|
9229
|
+
stopOpencodeTelegramIngest(codeName, log);
|
|
8442
9230
|
agentFrameworkCache.delete(codeName);
|
|
8443
9231
|
kanbanBoardCache.delete(codeName);
|
|
8444
9232
|
clearKanbanNudgeState(codeName);
|
|
@@ -8468,7 +9256,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
8468
9256
|
var lastVersionCheckAt = 0;
|
|
8469
9257
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8470
9258
|
var lastResponsivenessProbeAt = 0;
|
|
8471
|
-
var agtCliVersion = true ? "0.28.
|
|
9259
|
+
var agtCliVersion = true ? "0.28.344" : "dev";
|
|
8472
9260
|
function resolveBrewPath(execFileSync2) {
|
|
8473
9261
|
try {
|
|
8474
9262
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -8481,7 +9269,7 @@ function resolveBrewPath(execFileSync2) {
|
|
|
8481
9269
|
"/usr/local/bin/brew"
|
|
8482
9270
|
];
|
|
8483
9271
|
for (const path of fallbacks) {
|
|
8484
|
-
if (
|
|
9272
|
+
if (existsSync13(path)) return path;
|
|
8485
9273
|
}
|
|
8486
9274
|
return null;
|
|
8487
9275
|
}
|
|
@@ -8491,7 +9279,7 @@ function claudeBinaryInstalled(execFileSync2) {
|
|
|
8491
9279
|
"/opt/homebrew/bin/claude",
|
|
8492
9280
|
"/usr/local/bin/claude"
|
|
8493
9281
|
];
|
|
8494
|
-
if (canonical.some((path) =>
|
|
9282
|
+
if (canonical.some((path) => existsSync13(path))) return true;
|
|
8495
9283
|
try {
|
|
8496
9284
|
execFileSync2("which", ["claude"], { timeout: 5e3 });
|
|
8497
9285
|
return true;
|
|
@@ -8646,8 +9434,8 @@ function claudeManagedSettingsPath() {
|
|
|
8646
9434
|
function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
8647
9435
|
try {
|
|
8648
9436
|
let settings = {};
|
|
8649
|
-
if (
|
|
8650
|
-
const raw =
|
|
9437
|
+
if (existsSync13(path)) {
|
|
9438
|
+
const raw = readFileSync19(path, "utf-8").trim();
|
|
8651
9439
|
if (raw) {
|
|
8652
9440
|
let parsed;
|
|
8653
9441
|
try {
|
|
@@ -8663,8 +9451,8 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
|
8663
9451
|
}
|
|
8664
9452
|
if (settings.channelsEnabled === true) return "ok";
|
|
8665
9453
|
settings.channelsEnabled = true;
|
|
8666
|
-
|
|
8667
|
-
|
|
9454
|
+
mkdirSync9(dirname6(path), { recursive: true });
|
|
9455
|
+
writeFileSync10(path, `${JSON.stringify(settings, null, 2)}
|
|
8668
9456
|
`);
|
|
8669
9457
|
log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
|
|
8670
9458
|
return "ok";
|
|
@@ -8709,7 +9497,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
8709
9497
|
if (!process.env.PATH?.split(":").includes(brewBinDir)) {
|
|
8710
9498
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
8711
9499
|
}
|
|
8712
|
-
if (
|
|
9500
|
+
if (existsSync13("/home/linuxbrew/.linuxbrew/bin/claude")) {
|
|
8713
9501
|
log("Claude Code installed successfully");
|
|
8714
9502
|
} else {
|
|
8715
9503
|
log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
|
|
@@ -8765,7 +9553,7 @@ ${r.stderr}`;
|
|
|
8765
9553
|
}
|
|
8766
9554
|
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8767
9555
|
function selfUpdateAppliedMarkerPath() {
|
|
8768
|
-
return
|
|
9556
|
+
return join22(homedir11(), ".augmented", ".last-self-update-applied");
|
|
8769
9557
|
}
|
|
8770
9558
|
var selfUpdateUpToDateLogged = false;
|
|
8771
9559
|
var selfUpdatePinnedLogged = false;
|
|
@@ -8791,7 +9579,7 @@ async function checkAndUpdateCli(opts) {
|
|
|
8791
9579
|
const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
|
|
8792
9580
|
if (!isBrewFormula && !isNpmGlobal) return "noop";
|
|
8793
9581
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
8794
|
-
const markerPath =
|
|
9582
|
+
const markerPath = join22(homedir11(), ".augmented", ".last-update-check");
|
|
8795
9583
|
if (!force) {
|
|
8796
9584
|
try {
|
|
8797
9585
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
@@ -9136,14 +9924,14 @@ async function checkClaudeAuth() {
|
|
|
9136
9924
|
}
|
|
9137
9925
|
var evalEmptyMcpConfigPath = null;
|
|
9138
9926
|
function ensureEvalEmptyMcpConfig() {
|
|
9139
|
-
if (evalEmptyMcpConfigPath &&
|
|
9140
|
-
const dir =
|
|
9927
|
+
if (evalEmptyMcpConfigPath && existsSync13(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
|
|
9928
|
+
const dir = join22(homedir11(), ".augmented");
|
|
9141
9929
|
try {
|
|
9142
|
-
|
|
9930
|
+
mkdirSync9(dir, { recursive: true });
|
|
9143
9931
|
} catch {
|
|
9144
9932
|
}
|
|
9145
|
-
const p =
|
|
9146
|
-
|
|
9933
|
+
const p = join22(dir, ".eval-empty-mcp.json");
|
|
9934
|
+
writeFileSync10(p, JSON.stringify({ mcpServers: {} }));
|
|
9147
9935
|
evalEmptyMcpConfigPath = p;
|
|
9148
9936
|
return p;
|
|
9149
9937
|
}
|
|
@@ -9234,10 +10022,10 @@ function resolveConversationEvalBackend() {
|
|
|
9234
10022
|
return conversationEvalBackend;
|
|
9235
10023
|
}
|
|
9236
10024
|
function getStateFile() {
|
|
9237
|
-
return
|
|
10025
|
+
return join22(config?.configDir ?? join22(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
|
|
9238
10026
|
}
|
|
9239
10027
|
function channelHashCacheDir() {
|
|
9240
|
-
return config?.configDir ??
|
|
10028
|
+
return config?.configDir ?? join22(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
9241
10029
|
}
|
|
9242
10030
|
function loadChannelHashCache2() {
|
|
9243
10031
|
loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
|
|
@@ -9289,7 +10077,7 @@ function removeDeliveryBaselineEntries(agentId) {
|
|
|
9289
10077
|
var _channelQuarantineStore = null;
|
|
9290
10078
|
function channelQuarantineStore() {
|
|
9291
10079
|
if (!_channelQuarantineStore) {
|
|
9292
|
-
const dir = config?.configDir ??
|
|
10080
|
+
const dir = config?.configDir ?? join22(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
9293
10081
|
_channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
|
|
9294
10082
|
}
|
|
9295
10083
|
return _channelQuarantineStore;
|
|
@@ -9297,7 +10085,7 @@ function channelQuarantineStore() {
|
|
|
9297
10085
|
var _hostFlagStore = null;
|
|
9298
10086
|
function hostFlagStore() {
|
|
9299
10087
|
if (!_hostFlagStore) {
|
|
9300
|
-
const dir = config?.configDir ??
|
|
10088
|
+
const dir = config?.configDir ?? join22(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
9301
10089
|
_hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
|
|
9302
10090
|
}
|
|
9303
10091
|
return _hostFlagStore;
|
|
@@ -9364,13 +10152,13 @@ function parseSkillFrontmatter(content) {
|
|
|
9364
10152
|
return out;
|
|
9365
10153
|
}
|
|
9366
10154
|
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
9367
|
-
const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync:
|
|
9368
|
-
const skillsDir =
|
|
9369
|
-
const claudeMdPath =
|
|
10155
|
+
const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync11 } = await import("fs");
|
|
10156
|
+
const skillsDir = join22(configDir, codeName, "project", ".claude", "skills");
|
|
10157
|
+
const claudeMdPath = join22(configDir, codeName, "project", "CLAUDE.md");
|
|
9370
10158
|
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
9371
10159
|
const entries = [];
|
|
9372
10160
|
for (const dir of readdirSync6(skillsDir).sort()) {
|
|
9373
|
-
const skillFile =
|
|
10161
|
+
const skillFile = join22(skillsDir, dir, "SKILL.md");
|
|
9374
10162
|
if (!ex(skillFile)) continue;
|
|
9375
10163
|
try {
|
|
9376
10164
|
const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
|
|
@@ -9412,7 +10200,7 @@ ${SKILLS_INDEX_END}`;
|
|
|
9412
10200
|
next = current.trimEnd() + "\n\n" + section + "\n";
|
|
9413
10201
|
}
|
|
9414
10202
|
if (next !== current) {
|
|
9415
|
-
|
|
10203
|
+
writeFileSync11(claudeMdPath, next, "utf-8");
|
|
9416
10204
|
log2(`Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)`);
|
|
9417
10205
|
}
|
|
9418
10206
|
}
|
|
@@ -9433,7 +10221,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
9433
10221
|
if (codeNames.length === 0) return;
|
|
9434
10222
|
void (async () => {
|
|
9435
10223
|
try {
|
|
9436
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
10224
|
+
const { collectDiagnostics } = await import("../persistent-session-IDWQO7QL.js");
|
|
9437
10225
|
await api.post("/host/heartbeat", {
|
|
9438
10226
|
host_id: hostId,
|
|
9439
10227
|
agent_diagnostics: collectDiagnostics(codeNames)
|
|
@@ -9532,7 +10320,7 @@ async function pollCycle() {
|
|
|
9532
10320
|
}
|
|
9533
10321
|
try {
|
|
9534
10322
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
9535
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
10323
|
+
const { collectDiagnostics } = await import("../persistent-session-IDWQO7QL.js");
|
|
9536
10324
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
9537
10325
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
9538
10326
|
let tailscaleHostname;
|
|
@@ -9562,7 +10350,7 @@ async function pollCycle() {
|
|
|
9562
10350
|
claudeAuth = await detectClaudeAuth();
|
|
9563
10351
|
} catch (err) {
|
|
9564
10352
|
const errText = err instanceof Error ? err.message : String(err);
|
|
9565
|
-
const errId =
|
|
10353
|
+
const errId = createHash14("sha256").update(errText).digest("hex").slice(0, 12);
|
|
9566
10354
|
log(`Claude auth detection failed (error_id=${errId})`);
|
|
9567
10355
|
}
|
|
9568
10356
|
const hostHasClaudeCode = state6.agents.some(
|
|
@@ -9687,7 +10475,7 @@ async function pollCycle() {
|
|
|
9687
10475
|
const {
|
|
9688
10476
|
collectResponsivenessProbes,
|
|
9689
10477
|
getResponsivenessIntervalMs
|
|
9690
|
-
} = await import("../responsiveness-probe-
|
|
10478
|
+
} = await import("../responsiveness-probe-WF3SMLS6.js");
|
|
9691
10479
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
9692
10480
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
9693
10481
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -9719,8 +10507,8 @@ async function pollCycle() {
|
|
|
9719
10507
|
collectResponsivenessProbes,
|
|
9720
10508
|
livePendingInboundOldestAgeSeconds,
|
|
9721
10509
|
parkPendingInbound
|
|
9722
|
-
} = await import("../responsiveness-probe-
|
|
9723
|
-
const { getProjectDir: wedgeProjectDir } = await import("../
|
|
10510
|
+
} = await import("../responsiveness-probe-WF3SMLS6.js");
|
|
10511
|
+
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-HYOOQV4A.js");
|
|
9724
10512
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
9725
10513
|
const liveAgents = agentState.persistentSessionAgents;
|
|
9726
10514
|
for (const tracked of consecutiveWedgeCycles.keys()) {
|
|
@@ -9808,13 +10596,13 @@ async function pollCycle() {
|
|
|
9808
10596
|
);
|
|
9809
10597
|
if (hostFlagStore().getBoolean("wedge-transient-notice")) {
|
|
9810
10598
|
try {
|
|
9811
|
-
const paneTail =
|
|
10599
|
+
const paneTail = readFileSync19(paneLogPath(codeName), "utf8").slice(-65536);
|
|
9812
10600
|
const transient = detectTransientApiErrorInLog(paneTail);
|
|
9813
10601
|
if (transient) {
|
|
9814
|
-
const wedgeHome =
|
|
9815
|
-
if (
|
|
10602
|
+
const wedgeHome = join22(homedir11(), ".augmented", codeName);
|
|
10603
|
+
if (existsSync13(wedgeHome)) {
|
|
9816
10604
|
atomicWriteFileSync(
|
|
9817
|
-
|
|
10605
|
+
join22(wedgeHome, "watchdog-give-up.json"),
|
|
9818
10606
|
JSON.stringify({
|
|
9819
10607
|
gave_up_at: wedgeNow.toISOString(),
|
|
9820
10608
|
reason: "transient_overload"
|
|
@@ -10063,7 +10851,7 @@ async function pollCycle() {
|
|
|
10063
10851
|
} catch {
|
|
10064
10852
|
}
|
|
10065
10853
|
killAgentChannelProcesses(prev.codeName, { log });
|
|
10066
|
-
const agentDir =
|
|
10854
|
+
const agentDir = join22(adapter.getAgentDir(prev.codeName), "provision");
|
|
10067
10855
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
10068
10856
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
10069
10857
|
}
|
|
@@ -10150,10 +10938,10 @@ async function pollCycle() {
|
|
|
10150
10938
|
// pending-inbound marker. Best-effort: a write failure is logged by
|
|
10151
10939
|
// the watchdog, never fails the poll cycle.
|
|
10152
10940
|
signalGiveUp: (codeName) => {
|
|
10153
|
-
const dir =
|
|
10154
|
-
if (!
|
|
10941
|
+
const dir = join22(homedir11(), ".augmented", codeName);
|
|
10942
|
+
if (!existsSync13(dir)) return;
|
|
10155
10943
|
atomicWriteFileSync(
|
|
10156
|
-
|
|
10944
|
+
join22(dir, "watchdog-give-up.json"),
|
|
10157
10945
|
JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
10158
10946
|
);
|
|
10159
10947
|
}
|
|
@@ -10292,7 +11080,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10292
11080
|
}
|
|
10293
11081
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10294
11082
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
10295
|
-
let agentDir =
|
|
11083
|
+
let agentDir = join22(adapter.getAgentDir(agent.code_name), "provision");
|
|
10296
11084
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
10297
11085
|
if (previousKnownStatus !== agent.status) {
|
|
10298
11086
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -10338,7 +11126,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10338
11126
|
const residuals = {
|
|
10339
11127
|
gatewayRunning: false,
|
|
10340
11128
|
portAllocated: false,
|
|
10341
|
-
provisionDirExists:
|
|
11129
|
+
provisionDirExists: existsSync13(agentDir)
|
|
10342
11130
|
};
|
|
10343
11131
|
if (!hasRevokedResiduals(residuals)) {
|
|
10344
11132
|
agentStates.push({
|
|
@@ -10476,7 +11264,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10476
11264
|
const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
|
|
10477
11265
|
agentFrameworkCache.set(agent.code_name, frameworkId);
|
|
10478
11266
|
const frameworkAdapter = getFramework(frameworkId);
|
|
10479
|
-
agentDir =
|
|
11267
|
+
agentDir = join22(frameworkAdapter.getAgentDir(agent.code_name), "provision");
|
|
10480
11268
|
cacheAgentDeliveryMetadata(agent.code_name, refreshData);
|
|
10481
11269
|
agentRestartTimezoneInputs.set(agent.code_name, {
|
|
10482
11270
|
agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
|
|
@@ -10521,9 +11309,9 @@ async function processAgent(agent, agentStates) {
|
|
|
10521
11309
|
try {
|
|
10522
11310
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
10523
11311
|
const changedFiles = [];
|
|
10524
|
-
|
|
11312
|
+
mkdirSync9(agentDir, { recursive: true });
|
|
10525
11313
|
for (const artifact of artifacts) {
|
|
10526
|
-
const filePath =
|
|
11314
|
+
const filePath = join22(agentDir, artifact.relativePath);
|
|
10527
11315
|
let existingHash;
|
|
10528
11316
|
let newHash;
|
|
10529
11317
|
let writeContent = artifact.content;
|
|
@@ -10542,8 +11330,8 @@ async function processAgent(agent, agentStates) {
|
|
|
10542
11330
|
};
|
|
10543
11331
|
newHash = sha256(stripDynamicSections(artifact.content));
|
|
10544
11332
|
try {
|
|
10545
|
-
const projectClaudeMd =
|
|
10546
|
-
const existing =
|
|
11333
|
+
const projectClaudeMd = join22(config.configDir, agent.code_name, "project", "CLAUDE.md");
|
|
11334
|
+
const existing = readFileSync19(projectClaudeMd, "utf-8");
|
|
10547
11335
|
existingHash = sha256(stripDynamicSections(existing));
|
|
10548
11336
|
} catch {
|
|
10549
11337
|
existingHash = null;
|
|
@@ -10561,7 +11349,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10561
11349
|
const generatorKeys = Object.keys(generatorServers);
|
|
10562
11350
|
let existingRaw = "";
|
|
10563
11351
|
try {
|
|
10564
|
-
existingRaw =
|
|
11352
|
+
existingRaw = readFileSync19(filePath, "utf-8");
|
|
10565
11353
|
} catch {
|
|
10566
11354
|
}
|
|
10567
11355
|
const existingServers = parseMcp(existingRaw);
|
|
@@ -10583,26 +11371,26 @@ async function processAgent(agent, agentStates) {
|
|
|
10583
11371
|
}
|
|
10584
11372
|
}
|
|
10585
11373
|
if (changedFiles.length > 0) {
|
|
10586
|
-
const isFirst = !
|
|
11374
|
+
const isFirst = !existsSync13(join22(agentDir, "CHARTER.md"));
|
|
10587
11375
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
10588
11376
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
10589
11377
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
10590
11378
|
for (const file of changedFiles) {
|
|
10591
|
-
const filePath =
|
|
10592
|
-
|
|
11379
|
+
const filePath = join22(agentDir, file.relativePath);
|
|
11380
|
+
mkdirSync9(dirname6(filePath), { recursive: true });
|
|
10593
11381
|
if (file.relativePath === ".mcp.json") {
|
|
10594
11382
|
safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
|
|
10595
11383
|
} else {
|
|
10596
|
-
|
|
11384
|
+
writeFileSync10(filePath, file.content);
|
|
10597
11385
|
}
|
|
10598
11386
|
}
|
|
10599
11387
|
try {
|
|
10600
|
-
const provSkillsDir =
|
|
10601
|
-
if (
|
|
11388
|
+
const provSkillsDir = join22(agentDir, ".claude", "skills");
|
|
11389
|
+
if (existsSync13(provSkillsDir)) {
|
|
10602
11390
|
for (const folder of readdirSync5(provSkillsDir)) {
|
|
10603
11391
|
if (folder.startsWith("knowledge-")) {
|
|
10604
11392
|
try {
|
|
10605
|
-
rmSync4(
|
|
11393
|
+
rmSync4(join22(provSkillsDir, folder), { recursive: true });
|
|
10606
11394
|
} catch {
|
|
10607
11395
|
}
|
|
10608
11396
|
}
|
|
@@ -10615,7 +11403,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10615
11403
|
const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
|
|
10616
11404
|
const hashes = /* @__PURE__ */ new Map();
|
|
10617
11405
|
for (const file of trackedFiles2) {
|
|
10618
|
-
const h = hashFile(
|
|
11406
|
+
const h = hashFile(join22(agentDir, file));
|
|
10619
11407
|
if (h) hashes.set(file, h);
|
|
10620
11408
|
}
|
|
10621
11409
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -10633,14 +11421,14 @@ async function processAgent(agent, agentStates) {
|
|
|
10633
11421
|
}
|
|
10634
11422
|
if (Array.isArray(refreshData.workflows)) {
|
|
10635
11423
|
try {
|
|
10636
|
-
const provWorkflowsDir =
|
|
10637
|
-
if (
|
|
11424
|
+
const provWorkflowsDir = join22(agentDir, ".claude", "workflows");
|
|
11425
|
+
if (existsSync13(provWorkflowsDir)) {
|
|
10638
11426
|
const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
|
|
10639
11427
|
for (const file of readdirSync5(provWorkflowsDir)) {
|
|
10640
11428
|
if (!file.endsWith(".js")) continue;
|
|
10641
11429
|
if (expected.has(file)) continue;
|
|
10642
11430
|
try {
|
|
10643
|
-
rmSync4(
|
|
11431
|
+
rmSync4(join22(provWorkflowsDir, file));
|
|
10644
11432
|
} catch {
|
|
10645
11433
|
}
|
|
10646
11434
|
}
|
|
@@ -10697,10 +11485,10 @@ async function processAgent(agent, agentStates) {
|
|
|
10697
11485
|
}
|
|
10698
11486
|
let lastDriftCheckAt = now;
|
|
10699
11487
|
const written = agentState.writtenHashes.get(agent.agent_id);
|
|
10700
|
-
if (written &&
|
|
11488
|
+
if (written && existsSync13(agentDir)) {
|
|
10701
11489
|
const driftedFiles = [];
|
|
10702
11490
|
for (const [file, expectedHash] of written) {
|
|
10703
|
-
const localHash = hashFile(
|
|
11491
|
+
const localHash = hashFile(join22(agentDir, file));
|
|
10704
11492
|
if (localHash && localHash !== expectedHash) {
|
|
10705
11493
|
driftedFiles.push(file);
|
|
10706
11494
|
}
|
|
@@ -10711,7 +11499,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10711
11499
|
try {
|
|
10712
11500
|
const localHashes = {};
|
|
10713
11501
|
for (const file of driftedFiles) {
|
|
10714
|
-
localHashes[file] = hashFile(
|
|
11502
|
+
localHashes[file] = hashFile(join22(agentDir, file));
|
|
10715
11503
|
}
|
|
10716
11504
|
await api.post("/host/drift", {
|
|
10717
11505
|
agent_id: agent.agent_id,
|
|
@@ -10898,15 +11686,15 @@ async function processAgent(agent, agentStates) {
|
|
|
10898
11686
|
const addedChannels = [...restartDecision.added];
|
|
10899
11687
|
const writeDmNoticeMarkers = isChannelAddRestart ? () => {
|
|
10900
11688
|
try {
|
|
10901
|
-
const agentAugmentedDir =
|
|
10902
|
-
|
|
11689
|
+
const agentAugmentedDir = join22(homedir11(), ".augmented", agent.code_name);
|
|
11690
|
+
mkdirSync9(agentAugmentedDir, { recursive: true });
|
|
10903
11691
|
const markerJson = JSON.stringify({
|
|
10904
11692
|
version: 1,
|
|
10905
11693
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10906
11694
|
added: addedChannels
|
|
10907
11695
|
});
|
|
10908
11696
|
for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
|
|
10909
|
-
atomicWriteFileSync(
|
|
11697
|
+
atomicWriteFileSync(join22(agentAugmentedDir, file), markerJson);
|
|
10910
11698
|
}
|
|
10911
11699
|
} catch (err) {
|
|
10912
11700
|
log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
|
|
@@ -10982,7 +11770,7 @@ async function processAgent(agent, agentStates) {
|
|
|
10982
11770
|
const behaviourSubset = extractMsTeamsBehaviourSubset(
|
|
10983
11771
|
msteamsEntry?.config
|
|
10984
11772
|
);
|
|
10985
|
-
const behaviourHash =
|
|
11773
|
+
const behaviourHash = createHash14("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
|
|
10986
11774
|
const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
|
|
10987
11775
|
const msteamsBehaviourRestrictive = isMsTeamsBehaviourRestrictive(behaviourSubset);
|
|
10988
11776
|
const behaviourDecision = decideSenderPolicyRestart({
|
|
@@ -11038,7 +11826,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11038
11826
|
const slackBehaviourSubset = extractSlackBehaviourSubset(
|
|
11039
11827
|
slackEntry?.config
|
|
11040
11828
|
);
|
|
11041
|
-
const slackBehaviourHash =
|
|
11829
|
+
const slackBehaviourHash = createHash14("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
|
|
11042
11830
|
const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
|
|
11043
11831
|
const slackBehaviourRestrictive = isSlackBehaviourRestrictive(slackBehaviourSubset);
|
|
11044
11832
|
const slackBehaviourDecision = decideSenderPolicyRestart({
|
|
@@ -11095,24 +11883,24 @@ async function processAgent(agent, agentStates) {
|
|
|
11095
11883
|
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
|
|
11096
11884
|
try {
|
|
11097
11885
|
const agentProvisionDir = agentDir;
|
|
11098
|
-
const projectDir =
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
const provisionMcpPath =
|
|
11102
|
-
const projectMcpPath =
|
|
11886
|
+
const projectDir = join22(homedir11(), ".augmented", agent.code_name, "project");
|
|
11887
|
+
mkdirSync9(agentProvisionDir, { recursive: true });
|
|
11888
|
+
mkdirSync9(projectDir, { recursive: true });
|
|
11889
|
+
const provisionMcpPath = join22(agentProvisionDir, ".mcp.json");
|
|
11890
|
+
const projectMcpPath = join22(projectDir, ".mcp.json");
|
|
11103
11891
|
let mcpConfig = { mcpServers: {} };
|
|
11104
11892
|
try {
|
|
11105
|
-
mcpConfig = JSON.parse(
|
|
11893
|
+
mcpConfig = JSON.parse(readFileSync19(provisionMcpPath, "utf-8"));
|
|
11106
11894
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
11107
11895
|
} catch {
|
|
11108
11896
|
}
|
|
11109
|
-
const localDirectChatChannel =
|
|
11897
|
+
const localDirectChatChannel = join22(homedir11(), ".augmented", "_mcp", "direct-chat-channel.js");
|
|
11110
11898
|
const directChatTeamSettings = refreshData.team?.settings;
|
|
11111
11899
|
const directChatTz = (() => {
|
|
11112
11900
|
const tz = directChatTeamSettings?.["timezone"];
|
|
11113
11901
|
return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
|
|
11114
11902
|
})();
|
|
11115
|
-
if (
|
|
11903
|
+
if (existsSync13(localDirectChatChannel)) {
|
|
11116
11904
|
const directChatEnv = {
|
|
11117
11905
|
AGT_HOST: requireHost(),
|
|
11118
11906
|
// ENG-5901 Track D: templated — the manager exports the real
|
|
@@ -11132,7 +11920,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11132
11920
|
// ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
|
|
11133
11921
|
// returns the agent root (NOT the /provision subdir `agentDir` points at),
|
|
11134
11922
|
// so it byte-matches the broker readers' path.
|
|
11135
|
-
AGT_TURN_INITIATOR_FILE:
|
|
11923
|
+
AGT_TURN_INITIATOR_FILE: join22(
|
|
11136
11924
|
frameworkAdapter.getAgentDir(agent.code_name),
|
|
11137
11925
|
".current-turn-initiator.json"
|
|
11138
11926
|
)
|
|
@@ -11152,8 +11940,8 @@ async function processAgent(agent, agentStates) {
|
|
|
11152
11940
|
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
11153
11941
|
}
|
|
11154
11942
|
}
|
|
11155
|
-
const staleChannelsPath =
|
|
11156
|
-
if (
|
|
11943
|
+
const staleChannelsPath = join22(projectDir, ".mcp-channels.json");
|
|
11944
|
+
if (existsSync13(staleChannelsPath)) {
|
|
11157
11945
|
try {
|
|
11158
11946
|
rmSync4(staleChannelsPath, { force: true });
|
|
11159
11947
|
} catch {
|
|
@@ -11242,7 +12030,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11242
12030
|
}
|
|
11243
12031
|
if (hostFlagStore().getBoolean("connectivity-probe")) {
|
|
11244
12032
|
try {
|
|
11245
|
-
const probeProjectDir =
|
|
12033
|
+
const probeProjectDir = join22(homedir11(), ".augmented", agent.code_name, "project");
|
|
11246
12034
|
await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
|
|
11247
12035
|
} catch (err) {
|
|
11248
12036
|
log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -11253,7 +12041,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11253
12041
|
const forceDue = attemptsLeft > 0;
|
|
11254
12042
|
let probeRan = false;
|
|
11255
12043
|
try {
|
|
11256
|
-
const probeProjectDir =
|
|
12044
|
+
const probeProjectDir = join22(homedir11(), ".augmented", agent.code_name, "project");
|
|
11257
12045
|
probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
|
|
11258
12046
|
} catch (err) {
|
|
11259
12047
|
log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -11266,9 +12054,9 @@ async function processAgent(agent, agentStates) {
|
|
|
11266
12054
|
}
|
|
11267
12055
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
|
|
11268
12056
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
11269
|
-
if (mcpPath &&
|
|
12057
|
+
if (mcpPath && existsSync13(mcpPath)) {
|
|
11270
12058
|
try {
|
|
11271
|
-
const cfg = JSON.parse(
|
|
12059
|
+
const cfg = JSON.parse(readFileSync19(mcpPath, "utf-8"));
|
|
11272
12060
|
const expectedRemoteKeys = new Set(
|
|
11273
12061
|
integrations.map((i) => i.definition_id)
|
|
11274
12062
|
);
|
|
@@ -11293,11 +12081,11 @@ async function processAgent(agent, agentStates) {
|
|
|
11293
12081
|
recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
|
|
11294
12082
|
}
|
|
11295
12083
|
if (intHash !== prevIntHash) {
|
|
11296
|
-
const projectDir =
|
|
11297
|
-
const envIntPath =
|
|
12084
|
+
const projectDir = join22(homedir11(), ".augmented", agent.code_name, "project");
|
|
12085
|
+
const envIntPath = join22(projectDir, ".env.integrations");
|
|
11298
12086
|
let preWriteEnv;
|
|
11299
12087
|
try {
|
|
11300
|
-
preWriteEnv =
|
|
12088
|
+
preWriteEnv = readFileSync19(envIntPath, "utf-8");
|
|
11301
12089
|
} catch {
|
|
11302
12090
|
preWriteEnv = void 0;
|
|
11303
12091
|
}
|
|
@@ -11309,9 +12097,9 @@ async function processAgent(agent, agentStates) {
|
|
|
11309
12097
|
let rotationHandled = true;
|
|
11310
12098
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
11311
12099
|
try {
|
|
11312
|
-
const projectMcpPath =
|
|
11313
|
-
const postWriteEnv =
|
|
11314
|
-
const mcpContent =
|
|
12100
|
+
const projectMcpPath = join22(projectDir, ".mcp.json");
|
|
12101
|
+
const postWriteEnv = readFileSync19(envIntPath, "utf-8");
|
|
12102
|
+
const mcpContent = readFileSync19(projectMcpPath, "utf-8");
|
|
11315
12103
|
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
11316
12104
|
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
11317
12105
|
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
@@ -11388,10 +12176,10 @@ async function processAgent(agent, agentStates) {
|
|
|
11388
12176
|
desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
|
|
11389
12177
|
}
|
|
11390
12178
|
const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
|
|
11391
|
-
const headersHash =
|
|
12179
|
+
const headersHash = createHash14("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
|
|
11392
12180
|
return `${e.serverId}|${e.url}|${headersHash}`;
|
|
11393
12181
|
}).join("\n");
|
|
11394
|
-
const mcpHash =
|
|
12182
|
+
const mcpHash = createHash14("sha256").update(hashBasis).digest("hex").slice(0, 16);
|
|
11395
12183
|
const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
|
|
11396
12184
|
const structureHash = managedMcpStructureHash(desiredEntries);
|
|
11397
12185
|
const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
|
|
@@ -11406,15 +12194,15 @@ async function processAgent(agent, agentStates) {
|
|
|
11406
12194
|
if (mcpHash !== prevMcpHash) {
|
|
11407
12195
|
for (const e of desiredEntries) {
|
|
11408
12196
|
frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
|
|
11409
|
-
const urlHash =
|
|
12197
|
+
const urlHash = createHash14("sha256").update(e.url).digest("hex").slice(0, 12);
|
|
11410
12198
|
log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
|
|
11411
12199
|
}
|
|
11412
12200
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
|
|
11413
12201
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
11414
12202
|
if (mcpPath) {
|
|
11415
12203
|
try {
|
|
11416
|
-
const { readFileSync:
|
|
11417
|
-
const mcpConfig = JSON.parse(
|
|
12204
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
12205
|
+
const mcpConfig = JSON.parse(readFileSync20(mcpPath, "utf-8"));
|
|
11418
12206
|
if (mcpConfig.mcpServers) {
|
|
11419
12207
|
for (const key of Object.keys(mcpConfig.mcpServers)) {
|
|
11420
12208
|
if (isManagedMcpServerKey(key) && !expectedServerIds.has(key)) {
|
|
@@ -11515,7 +12303,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11515
12303
|
if (frameworkAdapter.installSkillFiles) {
|
|
11516
12304
|
const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
|
|
11517
12305
|
const installedIntegrationSkills = [];
|
|
11518
|
-
const { createHash:
|
|
12306
|
+
const { createHash: createHash15 } = await import("crypto");
|
|
11519
12307
|
const refreshAny = refreshData;
|
|
11520
12308
|
const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
|
|
11521
12309
|
const contextBySlug = /* @__PURE__ */ new Map();
|
|
@@ -11544,7 +12332,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11544
12332
|
)
|
|
11545
12333
|
}));
|
|
11546
12334
|
const bundle = buildIntegrationBundle(renderedScopes);
|
|
11547
|
-
const contentHash =
|
|
12335
|
+
const contentHash = createHash15("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
11548
12336
|
const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
|
|
11549
12337
|
if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
11550
12338
|
frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
|
|
@@ -11561,14 +12349,14 @@ async function processAgent(agent, agentStates) {
|
|
|
11561
12349
|
const frameworkId2 = frameworkAdapter.id;
|
|
11562
12350
|
const candidateSkillDirs = [
|
|
11563
12351
|
// Claude Code — framework runtime tree
|
|
11564
|
-
|
|
12352
|
+
join22(homedir12(), ".augmented", agent.code_name, "skills"),
|
|
11565
12353
|
// Claude Code — project tree
|
|
11566
|
-
|
|
12354
|
+
join22(homedir12(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
11567
12355
|
// Defensive: legacy provision-side path, not currently an
|
|
11568
12356
|
// install target but cheap to sweep.
|
|
11569
|
-
|
|
12357
|
+
join22(agentDir, ".claude", "skills")
|
|
11570
12358
|
];
|
|
11571
|
-
const existingDirs = candidateSkillDirs.filter((d) =>
|
|
12359
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync13(d));
|
|
11572
12360
|
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
11573
12361
|
for (const dir of existingDirs) {
|
|
11574
12362
|
try {
|
|
@@ -11582,8 +12370,8 @@ async function processAgent(agent, agentStates) {
|
|
|
11582
12370
|
}
|
|
11583
12371
|
const removeSkillFolder = (entry, reason) => {
|
|
11584
12372
|
for (const dir of existingDirs) {
|
|
11585
|
-
const p =
|
|
11586
|
-
if (
|
|
12373
|
+
const p = join22(dir, entry);
|
|
12374
|
+
if (existsSync13(p)) {
|
|
11587
12375
|
rmSync5(p, { recursive: true, force: true });
|
|
11588
12376
|
}
|
|
11589
12377
|
}
|
|
@@ -11602,7 +12390,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11602
12390
|
const sharedSkillsPayload = refreshAny.shared_skills;
|
|
11603
12391
|
const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
|
|
11604
12392
|
const manifestPath = managedSkillManifestPath(
|
|
11605
|
-
|
|
12393
|
+
join22(homedir11(), ".augmented", agent.code_name)
|
|
11606
12394
|
);
|
|
11607
12395
|
const prevIds = /* @__PURE__ */ new Set([
|
|
11608
12396
|
...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
|
|
@@ -11611,7 +12399,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11611
12399
|
const plan = planGlobalSkillSync(
|
|
11612
12400
|
[...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
|
|
11613
12401
|
prevIds,
|
|
11614
|
-
(content) =>
|
|
12402
|
+
(content) => createHash15("sha256").update(content).digest("hex").slice(0, 12),
|
|
11615
12403
|
(skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
|
|
11616
12404
|
{ desiredResolved }
|
|
11617
12405
|
);
|
|
@@ -11622,15 +12410,15 @@ async function processAgent(agent, agentStates) {
|
|
|
11622
12410
|
}
|
|
11623
12411
|
if (plan.removes.length) {
|
|
11624
12412
|
const globalSkillDirs = [
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
12413
|
+
join22(homedir11(), ".augmented", agent.code_name, "skills"),
|
|
12414
|
+
join22(homedir11(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
12415
|
+
join22(agentDir, ".claude", "skills")
|
|
11628
12416
|
];
|
|
11629
12417
|
for (const id of plan.removes) {
|
|
11630
12418
|
let prunedAny = false;
|
|
11631
12419
|
for (const dir of globalSkillDirs) {
|
|
11632
|
-
const p =
|
|
11633
|
-
if (
|
|
12420
|
+
const p = join22(dir, id);
|
|
12421
|
+
if (existsSync13(p) && existsSync13(join22(p, "SKILL.md"))) {
|
|
11634
12422
|
rmSync4(p, { recursive: true, force: true });
|
|
11635
12423
|
prunedAny = true;
|
|
11636
12424
|
}
|
|
@@ -11662,7 +12450,7 @@ async function processAgent(agent, agentStates) {
|
|
|
11662
12450
|
const slug = hook.integration_slug ?? hook.plugin_slug;
|
|
11663
12451
|
if (!slug) continue;
|
|
11664
12452
|
try {
|
|
11665
|
-
const scriptHash =
|
|
12453
|
+
const scriptHash = createHash15("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
11666
12454
|
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
|
|
11667
12455
|
if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
11668
12456
|
const result = await frameworkAdapter.executePluginHook({
|
|
@@ -11677,9 +12465,9 @@ async function processAgent(agent, agentStates) {
|
|
|
11677
12465
|
} else if (result.timedOut) {
|
|
11678
12466
|
log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
11679
12467
|
} else {
|
|
11680
|
-
const stderrHash =
|
|
12468
|
+
const stderrHash = createHash15("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
11681
12469
|
const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
|
|
11682
|
-
const missingCmdHash = missingCmd ?
|
|
12470
|
+
const missingCmdHash = missingCmd ? createHash15("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
|
|
11683
12471
|
log(
|
|
11684
12472
|
`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}]`
|
|
11685
12473
|
);
|
|
@@ -11836,8 +12624,8 @@ async function processAgent(agent, agentStates) {
|
|
|
11836
12624
|
const sess = getSessionState(agent.code_name);
|
|
11837
12625
|
let mcpJsonParsed = null;
|
|
11838
12626
|
try {
|
|
11839
|
-
const mcpPath =
|
|
11840
|
-
mcpJsonParsed = JSON.parse(
|
|
12627
|
+
const mcpPath = join22(getProjectDir(agent.code_name), ".mcp.json");
|
|
12628
|
+
mcpJsonParsed = JSON.parse(readFileSync19(mcpPath, "utf-8"));
|
|
11841
12629
|
} catch {
|
|
11842
12630
|
}
|
|
11843
12631
|
reapMissingMcpSessions({
|
|
@@ -11983,6 +12771,9 @@ async function processAgent(agent, agentStates) {
|
|
|
11983
12771
|
log(`[mcp-presence-reaper] presence check failed for '${agent.code_name}': ${err.message}`);
|
|
11984
12772
|
}
|
|
11985
12773
|
}
|
|
12774
|
+
if (agentFw === "opencode" && tasks.length > 0) {
|
|
12775
|
+
await syncAndCheckOpencodeScheduler(agent, tasks, refreshData);
|
|
12776
|
+
}
|
|
11986
12777
|
} else if (agentFw === "claude-code" && tasks.length > 0) {
|
|
11987
12778
|
await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);
|
|
11988
12779
|
}
|
|
@@ -12163,10 +12954,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
12163
12954
|
}
|
|
12164
12955
|
}
|
|
12165
12956
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
12166
|
-
if (trackedFiles.length > 0 &&
|
|
12957
|
+
if (trackedFiles.length > 0 && existsSync13(agentDir)) {
|
|
12167
12958
|
const hashes = /* @__PURE__ */ new Map();
|
|
12168
12959
|
for (const file of trackedFiles) {
|
|
12169
|
-
const h = hashFile(
|
|
12960
|
+
const h = hashFile(join22(agentDir, file));
|
|
12170
12961
|
if (h) hashes.set(file, h);
|
|
12171
12962
|
}
|
|
12172
12963
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -12181,7 +12972,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
12181
12972
|
refreshData.agent.onboarding_state
|
|
12182
12973
|
);
|
|
12183
12974
|
const obStep = obState.step;
|
|
12184
|
-
const markerPath =
|
|
12975
|
+
const markerPath = join22(homedir11(), ".augmented", agent.code_name, "onboarding-drive.json");
|
|
12185
12976
|
const marker = readOnboardingDriveMarker(markerPath);
|
|
12186
12977
|
const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
|
|
12187
12978
|
if (decision.clearMarker) {
|
|
@@ -12246,10 +13037,12 @@ async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
|
|
|
12246
13037
|
const codeName = agent.code_name;
|
|
12247
13038
|
if (isOpencodeSessionHealthy(codeName)) {
|
|
12248
13039
|
await ensureOpencodeSlackIngest({ codeName, agentId: agent.agent_id, log });
|
|
13040
|
+
await ensureOpencodeTelegramIngest({ codeName, agentId: agent.agent_id, log });
|
|
12249
13041
|
return { decision: "healthy", spawnAttempted: false, sessionHealthyAfter: true };
|
|
12250
13042
|
}
|
|
12251
13043
|
stopOpencodeSlackIngest(codeName, log);
|
|
12252
|
-
|
|
13044
|
+
stopOpencodeTelegramIngest(codeName, log);
|
|
13045
|
+
const opencodeProjectDir = join22(getFramework("opencode").getAgentDir(codeName), "provision");
|
|
12253
13046
|
const openRouterRaw = refreshData.openrouter ?? null;
|
|
12254
13047
|
const serveEnv = {
|
|
12255
13048
|
AGT_HOST: requireHost(),
|
|
@@ -12289,8 +13082,8 @@ async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
|
|
|
12289
13082
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
12290
13083
|
const codeName = agent.code_name;
|
|
12291
13084
|
const projectDir = getProjectDir(codeName);
|
|
12292
|
-
const mcpConfigPath =
|
|
12293
|
-
const claudeMdPath =
|
|
13085
|
+
const mcpConfigPath = join22(projectDir, ".mcp.json");
|
|
13086
|
+
const claudeMdPath = join22(projectDir, "CLAUDE.md");
|
|
12294
13087
|
if (restartBreaker.isTripped(codeName)) {
|
|
12295
13088
|
const trip = restartBreaker.getTrip(codeName);
|
|
12296
13089
|
return {
|
|
@@ -12448,7 +13241,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
12448
13241
|
const ctx = getLastFailureContext(codeName);
|
|
12449
13242
|
const recovery = prepareForRespawn(codeName);
|
|
12450
13243
|
const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
|
|
12451
|
-
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${
|
|
13244
|
+
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash14("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
|
|
12452
13245
|
const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
|
|
12453
13246
|
const recoverySummary = recovery ? `; recovery=${recovery}` : "";
|
|
12454
13247
|
log(
|
|
@@ -12462,7 +13255,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash12("sha256")
|
|
|
12462
13255
|
);
|
|
12463
13256
|
getHostId().then((hostId) => {
|
|
12464
13257
|
if (!hostId) return;
|
|
12465
|
-
const paneTailHash = zombie.paneTail ? `sha256:${
|
|
13258
|
+
const paneTailHash = zombie.paneTail ? `sha256:${createHash14("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
|
|
12466
13259
|
return api.post("/host/events", {
|
|
12467
13260
|
host_id: hostId,
|
|
12468
13261
|
agent_code_name: codeName,
|
|
@@ -12611,7 +13404,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash12("sha256")
|
|
|
12611
13404
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
12612
13405
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
12613
13406
|
}
|
|
12614
|
-
const stableTasksHash =
|
|
13407
|
+
const stableTasksHash = createHash14("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
12615
13408
|
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
12616
13409
|
if (stableTasksHash !== prevHash) {
|
|
12617
13410
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
@@ -12828,7 +13621,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
12828
13621
|
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}`));
|
|
12829
13622
|
void (async () => {
|
|
12830
13623
|
try {
|
|
12831
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
13624
|
+
const { collectDiagnostics } = await import("../persistent-session-IDWQO7QL.js");
|
|
12832
13625
|
await api.post("/host/heartbeat", {
|
|
12833
13626
|
host_id: hostId,
|
|
12834
13627
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -12878,7 +13671,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
12878
13671
|
}
|
|
12879
13672
|
try {
|
|
12880
13673
|
const hostId = await getHostId();
|
|
12881
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
13674
|
+
const { collectDiagnostics } = await import("../persistent-session-IDWQO7QL.js");
|
|
12882
13675
|
await api.post("/host/heartbeat", {
|
|
12883
13676
|
host_id: hostId,
|
|
12884
13677
|
agent_diagnostics: collectDiagnostics([codeName])
|
|
@@ -13164,8 +13957,8 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
13164
13957
|
if (useDoorbell) {
|
|
13165
13958
|
try {
|
|
13166
13959
|
const doorbell = directChatDoorbellPath(agent.agentId, homedir11());
|
|
13167
|
-
|
|
13168
|
-
|
|
13960
|
+
mkdirSync9(dirname6(doorbell), { recursive: true });
|
|
13961
|
+
writeFileSync10(doorbell, String(Date.now()));
|
|
13169
13962
|
log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
|
|
13170
13963
|
return;
|
|
13171
13964
|
} catch (err) {
|
|
@@ -13274,8 +14067,8 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
|
|
|
13274
14067
|
if (run_id) openInjectedRunByCode.set(codeName, run_id);
|
|
13275
14068
|
try {
|
|
13276
14069
|
const doorbell = directChatDoorbellPath(agentId, homedir11());
|
|
13277
|
-
|
|
13278
|
-
|
|
14070
|
+
mkdirSync9(dirname6(doorbell), { recursive: true });
|
|
14071
|
+
writeFileSync10(doorbell, String(Date.now()));
|
|
13279
14072
|
} catch (err) {
|
|
13280
14073
|
log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
|
|
13281
14074
|
}
|
|
@@ -13376,7 +14169,7 @@ async function processClaudePairSessions(agents) {
|
|
|
13376
14169
|
killPairSession,
|
|
13377
14170
|
pairTmuxSession,
|
|
13378
14171
|
finalizeClaudePairOnboarding
|
|
13379
|
-
} = await import("../claude-pair-runtime-
|
|
14172
|
+
} = await import("../claude-pair-runtime-JU3CVFUU.js");
|
|
13380
14173
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
13381
14174
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
13382
14175
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -13550,8 +14343,15 @@ function generateArtifacts(agent, refreshData, adapter) {
|
|
|
13550
14343
|
log(
|
|
13551
14344
|
`[provision/active-tasks] agent=${agent.code_name} count=${activeTasksFromRefresh.length} estimated_tokens=${activeTasksTokens}`
|
|
13552
14345
|
);
|
|
14346
|
+
const provisionAgent = (() => {
|
|
14347
|
+
const rawAgent = refreshData.agent;
|
|
14348
|
+
if (rawAgent?.["framework"] !== "opencode") return rawAgent;
|
|
14349
|
+
const hostOverride = process.env["AGT_OPENCODE_MODEL"]?.trim();
|
|
14350
|
+
const resolved = hostOverride || resolveModelChain(refreshData).primary || rawAgent?.["primary_model"];
|
|
14351
|
+
return resolved ? { ...rawAgent, primary_model: resolved } : rawAgent;
|
|
14352
|
+
})();
|
|
13553
14353
|
const provisionInput = {
|
|
13554
|
-
agent:
|
|
14354
|
+
agent: provisionAgent,
|
|
13555
14355
|
charterFrontmatter,
|
|
13556
14356
|
charterContent,
|
|
13557
14357
|
toolsFrontmatter,
|
|
@@ -13619,8 +14419,8 @@ function parseMemoryFile(raw, fallbackName) {
|
|
|
13619
14419
|
};
|
|
13620
14420
|
}
|
|
13621
14421
|
async function syncMemories(agent, configDir, log2) {
|
|
13622
|
-
const projectDir =
|
|
13623
|
-
const memoryDir =
|
|
14422
|
+
const projectDir = join22(configDir, agent.code_name, "project");
|
|
14423
|
+
const memoryDir = join22(projectDir, "memory");
|
|
13624
14424
|
const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
|
|
13625
14425
|
if (isFreshSync) {
|
|
13626
14426
|
log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
|
|
@@ -13631,15 +14431,15 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
13631
14431
|
}
|
|
13632
14432
|
pendingFreshMemorySync.delete(agent.agent_id);
|
|
13633
14433
|
}
|
|
13634
|
-
if (
|
|
14434
|
+
if (existsSync13(memoryDir)) {
|
|
13635
14435
|
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
13636
14436
|
const currentHashes = /* @__PURE__ */ new Map();
|
|
13637
14437
|
const changedMemories = [];
|
|
13638
14438
|
for (const file of readdirSync5(memoryDir)) {
|
|
13639
14439
|
if (!file.endsWith(".md")) continue;
|
|
13640
14440
|
try {
|
|
13641
|
-
const raw =
|
|
13642
|
-
const fileHash =
|
|
14441
|
+
const raw = readFileSync19(join22(memoryDir, file), "utf-8");
|
|
14442
|
+
const fileHash = createHash14("sha256").update(raw).digest("hex").slice(0, 16);
|
|
13643
14443
|
currentHashes.set(file, fileHash);
|
|
13644
14444
|
if (prevHashes.get(file) === fileHash) continue;
|
|
13645
14445
|
const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
|
|
@@ -13663,7 +14463,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
13663
14463
|
} catch (err) {
|
|
13664
14464
|
for (const mem of changedMemories) {
|
|
13665
14465
|
for (const [file] of currentHashes) {
|
|
13666
|
-
const parsed = parseMemoryFile(
|
|
14466
|
+
const parsed = parseMemoryFile(readFileSync19(join22(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
13667
14467
|
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
13668
14468
|
}
|
|
13669
14469
|
}
|
|
@@ -13676,29 +14476,29 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
13676
14476
|
}
|
|
13677
14477
|
}
|
|
13678
14478
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
13679
|
-
const localFiles =
|
|
13680
|
-
const localListHash =
|
|
14479
|
+
const localFiles = existsSync13(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
14480
|
+
const localListHash = createHash14("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
13681
14481
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
13682
14482
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
13683
14483
|
try {
|
|
13684
14484
|
const dbMemories = await api.post("/host/memories", {
|
|
13685
14485
|
agent_id: agent.agent_id
|
|
13686
14486
|
});
|
|
13687
|
-
const responseHash =
|
|
14487
|
+
const responseHash = createHash14("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
|
|
13688
14488
|
if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
|
|
13689
14489
|
return true;
|
|
13690
14490
|
}
|
|
13691
14491
|
lastDownloadHash.set(agent.agent_id, responseHash);
|
|
13692
14492
|
lastLocalFileHash.set(agent.agent_id, localListHash);
|
|
13693
14493
|
if (dbMemories.memories?.length) {
|
|
13694
|
-
|
|
14494
|
+
mkdirSync9(memoryDir, { recursive: true });
|
|
13695
14495
|
let written = 0;
|
|
13696
14496
|
let overwritten = 0;
|
|
13697
14497
|
for (let i = 0; i < dbMemories.memories.length; i++) {
|
|
13698
14498
|
const mem = dbMemories.memories[i];
|
|
13699
14499
|
const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
13700
14500
|
const slug = rawSlug || `memory-${i}`;
|
|
13701
|
-
const filePath =
|
|
14501
|
+
const filePath = join22(memoryDir, `${slug}.md`);
|
|
13702
14502
|
const desired = `---
|
|
13703
14503
|
name: ${JSON.stringify(mem.name)}
|
|
13704
14504
|
type: ${mem.type}
|
|
@@ -13707,23 +14507,23 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
|
13707
14507
|
|
|
13708
14508
|
${mem.content}
|
|
13709
14509
|
`;
|
|
13710
|
-
if (
|
|
14510
|
+
if (existsSync13(filePath)) {
|
|
13711
14511
|
let existing = "";
|
|
13712
14512
|
try {
|
|
13713
|
-
existing =
|
|
14513
|
+
existing = readFileSync19(filePath, "utf-8");
|
|
13714
14514
|
} catch {
|
|
13715
14515
|
}
|
|
13716
14516
|
if (existing === desired) continue;
|
|
13717
|
-
|
|
14517
|
+
writeFileSync10(filePath, desired);
|
|
13718
14518
|
overwritten++;
|
|
13719
14519
|
} else {
|
|
13720
|
-
|
|
14520
|
+
writeFileSync10(filePath, desired);
|
|
13721
14521
|
written++;
|
|
13722
14522
|
}
|
|
13723
14523
|
}
|
|
13724
14524
|
if (written > 0 || overwritten > 0) {
|
|
13725
14525
|
const updatedFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
13726
|
-
lastLocalFileHash.set(agent.agent_id,
|
|
14526
|
+
lastLocalFileHash.set(agent.agent_id, createHash14("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
|
|
13727
14527
|
log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
|
|
13728
14528
|
}
|
|
13729
14529
|
}
|
|
@@ -13734,7 +14534,7 @@ ${mem.content}
|
|
|
13734
14534
|
}
|
|
13735
14535
|
}
|
|
13736
14536
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
13737
|
-
if (
|
|
14537
|
+
if (existsSync13(agentDir)) {
|
|
13738
14538
|
try {
|
|
13739
14539
|
rmSync4(agentDir, { recursive: true, force: true });
|
|
13740
14540
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -13956,6 +14756,7 @@ async function stopPolling(opts = {}) {
|
|
|
13956
14756
|
stopCaffeinate();
|
|
13957
14757
|
stopRealtimeChat();
|
|
13958
14758
|
stopAllOpencodeSlackIngests(log);
|
|
14759
|
+
stopAllOpencodeTelegramIngests(log);
|
|
13959
14760
|
log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
|
|
13960
14761
|
for (const codeName of [...scheduledRunsByCode.keys()]) {
|
|
13961
14762
|
closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
|
|
@@ -13970,8 +14771,8 @@ function startManager(opts) {
|
|
|
13970
14771
|
config = opts;
|
|
13971
14772
|
try {
|
|
13972
14773
|
const stateFile = getStateFile();
|
|
13973
|
-
if (
|
|
13974
|
-
const raw =
|
|
14774
|
+
if (existsSync13(stateFile)) {
|
|
14775
|
+
const raw = readFileSync19(stateFile, "utf-8");
|
|
13975
14776
|
const parsed = JSON.parse(raw);
|
|
13976
14777
|
if (Array.isArray(parsed.agents)) {
|
|
13977
14778
|
state6.agents = parsed.agents;
|
|
@@ -13998,7 +14799,7 @@ function startManager(opts) {
|
|
|
13998
14799
|
log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
|
|
13999
14800
|
}
|
|
14000
14801
|
log(
|
|
14001
|
-
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${
|
|
14802
|
+
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join22(homedir11(), ".augmented", "manager.log")}`
|
|
14002
14803
|
);
|
|
14003
14804
|
deployMcpAssets();
|
|
14004
14805
|
reapOrphanChannelMcps({ log });
|
|
@@ -14019,7 +14820,7 @@ async function reapOrphanedClaudePids() {
|
|
|
14019
14820
|
const looksLikeClaude = (pid) => {
|
|
14020
14821
|
if (process.platform !== "linux") return true;
|
|
14021
14822
|
try {
|
|
14022
|
-
const comm =
|
|
14823
|
+
const comm = readFileSync19(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
|
|
14023
14824
|
return comm.includes("claude");
|
|
14024
14825
|
} catch {
|
|
14025
14826
|
return false;
|
|
@@ -14116,14 +14917,14 @@ function restartRunningChannelMcps(basenames) {
|
|
|
14116
14917
|
}
|
|
14117
14918
|
}
|
|
14118
14919
|
function deployMcpAssets() {
|
|
14119
|
-
const targetDir =
|
|
14120
|
-
|
|
14920
|
+
const targetDir = join22(homedir11(), ".augmented", "_mcp");
|
|
14921
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
14121
14922
|
const moduleDir = dirname6(fileURLToPath(import.meta.url));
|
|
14122
14923
|
let mcpSourceDir = "";
|
|
14123
14924
|
let dir = moduleDir;
|
|
14124
14925
|
for (let i = 0; i < 6; i++) {
|
|
14125
|
-
const candidate =
|
|
14126
|
-
if (
|
|
14926
|
+
const candidate = join22(dir, "dist", "mcp");
|
|
14927
|
+
if (existsSync13(join22(candidate, "index.js"))) {
|
|
14127
14928
|
mcpSourceDir = candidate;
|
|
14128
14929
|
break;
|
|
14129
14930
|
}
|
|
@@ -14138,8 +14939,8 @@ function deployMcpAssets() {
|
|
|
14138
14939
|
const changedBasenames = [];
|
|
14139
14940
|
const fileHash = (p) => {
|
|
14140
14941
|
try {
|
|
14141
|
-
if (!
|
|
14142
|
-
return
|
|
14942
|
+
if (!existsSync13(p)) return null;
|
|
14943
|
+
return createHash14("sha256").update(readFileSync19(p)).digest("hex");
|
|
14143
14944
|
} catch {
|
|
14144
14945
|
return null;
|
|
14145
14946
|
}
|
|
@@ -14203,9 +15004,9 @@ function deployMcpAssets() {
|
|
|
14203
15004
|
// needs restarting to pick up a token rotation.
|
|
14204
15005
|
"xero.js"
|
|
14205
15006
|
]) {
|
|
14206
|
-
const src =
|
|
14207
|
-
const dst =
|
|
14208
|
-
if (!
|
|
15007
|
+
const src = join22(mcpSourceDir, file);
|
|
15008
|
+
const dst = join22(targetDir, file);
|
|
15009
|
+
if (!existsSync13(src)) continue;
|
|
14209
15010
|
const before = fileHash(dst);
|
|
14210
15011
|
try {
|
|
14211
15012
|
copyFileSync(src, dst);
|
|
@@ -14222,23 +15023,23 @@ function deployMcpAssets() {
|
|
|
14222
15023
|
log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
|
|
14223
15024
|
restartRunningChannelMcps(changedBasenames);
|
|
14224
15025
|
}
|
|
14225
|
-
const localMcpPath =
|
|
15026
|
+
const localMcpPath = join22(targetDir, "index.js");
|
|
14226
15027
|
try {
|
|
14227
|
-
const agentsDir =
|
|
14228
|
-
if (
|
|
15028
|
+
const agentsDir = join22(homedir11(), ".augmented", "agents");
|
|
15029
|
+
if (existsSync13(agentsDir)) {
|
|
14229
15030
|
for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
|
|
14230
15031
|
if (!entry.isDirectory()) continue;
|
|
14231
15032
|
for (const subdir of ["provision", "project"]) {
|
|
14232
|
-
const mcpJsonPath =
|
|
15033
|
+
const mcpJsonPath = join22(agentsDir, entry.name, subdir, ".mcp.json");
|
|
14233
15034
|
try {
|
|
14234
|
-
const raw =
|
|
15035
|
+
const raw = readFileSync19(mcpJsonPath, "utf-8");
|
|
14235
15036
|
if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
|
|
14236
15037
|
const mcpConfig = JSON.parse(raw);
|
|
14237
15038
|
const augServer = mcpConfig.mcpServers?.["augmented"];
|
|
14238
15039
|
if (!augServer) continue;
|
|
14239
15040
|
augServer.command = "node";
|
|
14240
15041
|
augServer.args = [localMcpPath];
|
|
14241
|
-
|
|
15042
|
+
writeFileSync10(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
14242
15043
|
log(`[manager] Patched ${entry.name}/${subdir}/.mcp.json: npx \u2192 node`);
|
|
14243
15044
|
} catch {
|
|
14244
15045
|
}
|