@integrity-labs/agt-cli 0.10.6 → 0.10.8
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 +3 -3
- package/dist/{chunk-O5T62RSQ.js → chunk-VDUA5FEW.js} +198 -14
- package/dist/chunk-VDUA5FEW.js.map +1 -0
- package/dist/lib/manager-worker.js +148 -3
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +41 -0
- package/package.json +1 -1
- package/dist/chunk-O5T62RSQ.js.map +0 -1
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
provisionStopHook,
|
|
11
11
|
requireHost,
|
|
12
12
|
resolveChannels
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-VDUA5FEW.js";
|
|
14
14
|
import {
|
|
15
15
|
findTaskByTemplate,
|
|
16
16
|
getProjectDir,
|
|
@@ -683,6 +683,9 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
683
683
|
lastHarvestAt.delete(codeName);
|
|
684
684
|
claudeSchedulerStates.delete(codeName);
|
|
685
685
|
claudeTaskConcurrency.delete(codeName);
|
|
686
|
+
memoryFileHashes.delete(agentId);
|
|
687
|
+
lastDownloadHash.delete(agentId);
|
|
688
|
+
lastLocalFileHash.delete(agentId);
|
|
686
689
|
for (const key of knownChannelConfigHashes.keys()) {
|
|
687
690
|
if (key.startsWith(`${agentId}:`)) knownChannelConfigHashes.delete(key);
|
|
688
691
|
}
|
|
@@ -1703,8 +1706,15 @@ async function processAgent(agent, agentStates) {
|
|
|
1703
1706
|
if (!pluginName) continue;
|
|
1704
1707
|
const entry = refreshData.channel_configs[channelId];
|
|
1705
1708
|
if (!entry || entry.status !== "active" && entry.status !== "pending") continue;
|
|
1706
|
-
|
|
1707
|
-
|
|
1709
|
+
try {
|
|
1710
|
+
const installedPath = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
1711
|
+
if (existsSync(installedPath)) {
|
|
1712
|
+
const installed = JSON.parse(readFileSync(installedPath, "utf-8"));
|
|
1713
|
+
const pluginKeys = Object.keys(installed.plugins ?? {});
|
|
1714
|
+
if (pluginKeys.some((k) => k.startsWith(`${pluginName}@`))) continue;
|
|
1715
|
+
}
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1708
1718
|
try {
|
|
1709
1719
|
const { execFileSync: efs } = await import("child_process");
|
|
1710
1720
|
const claudePath = efs("which", ["claude"], { timeout: 5e3 }).toString().trim();
|
|
@@ -2280,6 +2290,13 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
|
|
|
2280
2290
|
}
|
|
2281
2291
|
}
|
|
2282
2292
|
}
|
|
2293
|
+
if (frameworkId === "claude-code" && agent.status === "active") {
|
|
2294
|
+
try {
|
|
2295
|
+
await syncMemories(agent, config.configDir, log);
|
|
2296
|
+
} catch (err) {
|
|
2297
|
+
log(`Memory sync failed for '${agent.code_name}': ${err.message}`);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2283
2300
|
agentStates.push({
|
|
2284
2301
|
agentId: agent.agent_id,
|
|
2285
2302
|
codeName: agent.code_name,
|
|
@@ -2472,6 +2489,12 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
2472
2489
|
for (const task of ready) {
|
|
2473
2490
|
if (inFlightClaudeTasks.has(task.taskId)) continue;
|
|
2474
2491
|
if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;
|
|
2492
|
+
if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {
|
|
2493
|
+
log(`[claude-scheduler] Skipping '${task.name}' for '${codeName}' \u2014 board is empty`);
|
|
2494
|
+
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
2495
|
+
claudeSchedulerStates.set(codeName, updated);
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2475
2498
|
let prompt = task.prompt;
|
|
2476
2499
|
if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {
|
|
2477
2500
|
const template = PLAN_TEMPLATES.has(task.templateId) ? "morning-plan" : "follow-up";
|
|
@@ -2746,6 +2769,12 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
2746
2769
|
log(`[persistent-session] ${ready.length} ready task(s) for '${codeName}': ${ready.map((t) => `${t.name}(next=${t.nextFireAt ? new Date(t.nextFireAt).toISOString() : "null"})`).join(", ")}`);
|
|
2747
2770
|
}
|
|
2748
2771
|
for (const task of ready) {
|
|
2772
|
+
if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {
|
|
2773
|
+
log(`[persistent-session] Skipping '${task.name}' for '${codeName}' \u2014 board is empty`);
|
|
2774
|
+
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
2775
|
+
claudeSchedulerStates.set(codeName, updated);
|
|
2776
|
+
continue;
|
|
2777
|
+
}
|
|
2749
2778
|
let prompt = task.prompt;
|
|
2750
2779
|
if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {
|
|
2751
2780
|
const template = PLAN_TEMPLATES.has(task.templateId) ? "morning-plan" : "follow-up";
|
|
@@ -3137,6 +3166,10 @@ var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-upda
|
|
|
3137
3166
|
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
3138
3167
|
var KANBAN_WORK_TEMPLATES = /* @__PURE__ */ new Set(["kanban-work"]);
|
|
3139
3168
|
var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary", "kanban-work"]);
|
|
3169
|
+
var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["backlog", "today", "in_progress"]);
|
|
3170
|
+
function hasActionableItems(items) {
|
|
3171
|
+
return items.some((item) => ACTIONABLE_STATUSES.has(item.status));
|
|
3172
|
+
}
|
|
3140
3173
|
var lastHarvestAt = /* @__PURE__ */ new Map();
|
|
3141
3174
|
var HARVEST_INTERVAL_MS = 3 * 60 * 1e3;
|
|
3142
3175
|
var kanbanBoardCache = /* @__PURE__ */ new Map();
|
|
@@ -3820,6 +3853,118 @@ function generateArtifacts(agent, refreshData, adapter) {
|
|
|
3820
3853
|
const provisionOutput = provision(provisionInput, adapter.id);
|
|
3821
3854
|
return provisionOutput.artifacts;
|
|
3822
3855
|
}
|
|
3856
|
+
var memoryFileHashes = /* @__PURE__ */ new Map();
|
|
3857
|
+
var lastDownloadHash = /* @__PURE__ */ new Map();
|
|
3858
|
+
var lastLocalFileHash = /* @__PURE__ */ new Map();
|
|
3859
|
+
function parseMemoryFile(raw, fallbackName) {
|
|
3860
|
+
const trimmed = raw.trim();
|
|
3861
|
+
if (!trimmed) return null;
|
|
3862
|
+
const fmMatch = trimmed.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
3863
|
+
if (fmMatch) {
|
|
3864
|
+
const frontmatter = fmMatch[1] ?? "";
|
|
3865
|
+
const body = (fmMatch[2] ?? "").trim();
|
|
3866
|
+
if (!body) return null;
|
|
3867
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
3868
|
+
const typeMatch = frontmatter.match(/^type:\s*(.+)$/m);
|
|
3869
|
+
const name = nameMatch?.[1]?.trim().replace(/^["']|["']$/g, "") ?? fallbackName;
|
|
3870
|
+
const rawType = typeMatch?.[1]?.trim().replace(/^["']|["']$/g, "") ?? "user";
|
|
3871
|
+
const typeMap = { user: "user", feedback: "feedback", project: "project", reference: "reference", daily: "daily" };
|
|
3872
|
+
const type = typeMap[rawType] ?? "user";
|
|
3873
|
+
return { name, content: body, type };
|
|
3874
|
+
}
|
|
3875
|
+
const isDaily = /^\d{4}-\d{2}-\d{2}$/.test(fallbackName);
|
|
3876
|
+
return {
|
|
3877
|
+
name: fallbackName,
|
|
3878
|
+
content: trimmed,
|
|
3879
|
+
type: isDaily ? "daily" : "project"
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
async function syncMemories(agent, configDir, log2) {
|
|
3883
|
+
const projectDir = join(configDir, agent.code_name, "project");
|
|
3884
|
+
const memoryDir = join(projectDir, "memory");
|
|
3885
|
+
if (existsSync(memoryDir)) {
|
|
3886
|
+
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
3887
|
+
const currentHashes = /* @__PURE__ */ new Map();
|
|
3888
|
+
const changedMemories = [];
|
|
3889
|
+
for (const file of readdirSync(memoryDir)) {
|
|
3890
|
+
if (!file.endsWith(".md")) continue;
|
|
3891
|
+
try {
|
|
3892
|
+
const raw = readFileSync(join(memoryDir, file), "utf-8");
|
|
3893
|
+
const fileHash = createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
3894
|
+
currentHashes.set(file, fileHash);
|
|
3895
|
+
if (prevHashes.get(file) === fileHash) continue;
|
|
3896
|
+
const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
|
|
3897
|
+
if (parsed) {
|
|
3898
|
+
if (parsed.type === "daily") {
|
|
3899
|
+
parsed.expires_at = new Date(Date.now() + 48 * 60 * 60 * 1e3).toISOString();
|
|
3900
|
+
}
|
|
3901
|
+
changedMemories.push(parsed);
|
|
3902
|
+
}
|
|
3903
|
+
} catch {
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
memoryFileHashes.set(agent.agent_id, currentHashes);
|
|
3907
|
+
if (changedMemories.length > 0) {
|
|
3908
|
+
try {
|
|
3909
|
+
await api.post("/host/sync-memories", {
|
|
3910
|
+
agent_id: agent.agent_id,
|
|
3911
|
+
memories: changedMemories
|
|
3912
|
+
});
|
|
3913
|
+
log2(`Synced ${changedMemories.length} changed memories to DB for '${agent.code_name}'`);
|
|
3914
|
+
} catch (err) {
|
|
3915
|
+
for (const mem of changedMemories) {
|
|
3916
|
+
for (const [file] of currentHashes) {
|
|
3917
|
+
const parsed = parseMemoryFile(readFileSync(join(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
3918
|
+
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
log2(`Memory upload failed for '${agent.code_name}': ${err.message}`);
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
const localFiles = existsSync(memoryDir) ? readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
3926
|
+
const localListHash = createHash("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
3927
|
+
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
3928
|
+
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
3929
|
+
try {
|
|
3930
|
+
const dbMemories = await api.post("/host/memories", {
|
|
3931
|
+
agent_id: agent.agent_id
|
|
3932
|
+
});
|
|
3933
|
+
const responseHash = createHash("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
|
|
3934
|
+
if (prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
lastDownloadHash.set(agent.agent_id, responseHash);
|
|
3938
|
+
lastLocalFileHash.set(agent.agent_id, localListHash);
|
|
3939
|
+
if (dbMemories.memories?.length) {
|
|
3940
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
3941
|
+
const existingFileSet = new Set(localFiles.map((f) => f.replace(/\.md$/, "").toLowerCase()));
|
|
3942
|
+
let written = 0;
|
|
3943
|
+
for (const mem of dbMemories.memories) {
|
|
3944
|
+
const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
3945
|
+
const slug = rawSlug || `memory-${written}`;
|
|
3946
|
+
if (existingFileSet.has(slug)) continue;
|
|
3947
|
+
const fileContent = `---
|
|
3948
|
+
name: ${JSON.stringify(mem.name)}
|
|
3949
|
+
type: ${mem.type}
|
|
3950
|
+
description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
3951
|
+
---
|
|
3952
|
+
|
|
3953
|
+
${mem.content}
|
|
3954
|
+
`;
|
|
3955
|
+
writeFileSync(join(memoryDir, `${slug}.md`), fileContent);
|
|
3956
|
+
written++;
|
|
3957
|
+
}
|
|
3958
|
+
if (written > 0) {
|
|
3959
|
+
const updatedFiles = readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
3960
|
+
lastLocalFileHash.set(agent.agent_id, createHash("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
|
|
3961
|
+
log2(`Exported ${written} DB memories to local files for '${agent.code_name}'`);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
} catch (err) {
|
|
3965
|
+
log2(`Memory download failed for '${agent.code_name}': ${err.message}`);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3823
3968
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
3824
3969
|
if (existsSync(agentDir)) {
|
|
3825
3970
|
try {
|