@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.
@@ -10,7 +10,7 @@ import {
10
10
  provisionStopHook,
11
11
  requireHost,
12
12
  resolveChannels
13
- } from "../chunk-O5T62RSQ.js";
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
- const pluginDir = join(homedir(), ".claude", "plugins", pluginName);
1707
- if (existsSync(pluginDir)) continue;
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 {