@integrity-labs/agt-cli 0.28.666 → 0.28.667

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.
Files changed (24) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-FYZOD3SS.js → chunk-HXGW24VQ.js} +57 -1
  3. package/dist/chunk-HXGW24VQ.js.map +1 -0
  4. package/dist/{chunk-P7UIFZQO.js → chunk-S5KJIVSA.js} +4 -4
  5. package/dist/{chunk-EX5XJEGF.js → chunk-Y34UA33A.js} +2 -2
  6. package/dist/{claude-pair-runtime-XSUQMCIQ.js → claude-pair-runtime-IW4FGSBB.js} +2 -2
  7. package/dist/lib/manager-worker.js +234 -24
  8. package/dist/lib/manager-worker.js.map +1 -1
  9. package/dist/mcp/direct-chat-channel.js +56 -0
  10. package/dist/mcp/index.js +56 -0
  11. package/dist/mcp/origami.js +56 -0
  12. package/dist/mcp/slack-channel.js +56 -0
  13. package/dist/mcp/telegram-channel.js +56 -0
  14. package/dist/{persistent-session-SQWO4TKG.js → persistent-session-D4JOMLCY.js} +3 -3
  15. package/dist/{responsiveness-probe-RWMERSER.js → responsiveness-probe-WANP5PYP.js} +3 -3
  16. package/dist/{session-auth-dead-OOWR5BPI.js → session-auth-dead-2L7FWRLT.js} +2 -2
  17. package/package.json +1 -1
  18. package/dist/chunk-FYZOD3SS.js.map +0 -1
  19. /package/dist/{chunk-P7UIFZQO.js.map → chunk-S5KJIVSA.js.map} +0 -0
  20. /package/dist/{chunk-EX5XJEGF.js.map → chunk-Y34UA33A.js.map} +0 -0
  21. /package/dist/{claude-pair-runtime-XSUQMCIQ.js.map → claude-pair-runtime-IW4FGSBB.js.map} +0 -0
  22. /package/dist/{persistent-session-SQWO4TKG.js.map → persistent-session-D4JOMLCY.js.map} +0 -0
  23. /package/dist/{responsiveness-probe-RWMERSER.js.map → responsiveness-probe-WANP5PYP.js.map} +0 -0
  24. /package/dist/{session-auth-dead-OOWR5BPI.js.map → session-auth-dead-2L7FWRLT.js.map} +0 -0
@@ -8,7 +8,7 @@ import {
8
8
  isolationMode,
9
9
  log,
10
10
  parseEnvIntegrations
11
- } from "./chunk-EX5XJEGF.js";
11
+ } from "./chunk-Y34UA33A.js";
12
12
  import {
13
13
  BIND_FAILURE_QUARANTINE_THRESHOLD,
14
14
  INTEGRATIONS_SECTION_END,
@@ -55,7 +55,7 @@ import {
55
55
  resolveConnectivityProbe,
56
56
  worseConnectivityOutcome,
57
57
  wrapScheduledTaskPrompt
58
- } from "./chunk-FYZOD3SS.js";
58
+ } from "./chunk-HXGW24VQ.js";
59
59
  import {
60
60
  parsePsRows
61
61
  } from "./chunk-XWVM4KPK.js";
@@ -6342,7 +6342,7 @@ function exchangeFailureKind(err) {
6342
6342
  }
6343
6343
 
6344
6344
  // src/lib/api-client.ts
6345
- var agtCliVersion = true ? "0.28.666" : "dev";
6345
+ var agtCliVersion = true ? "0.28.667" : "dev";
6346
6346
  var lastConfigHash = null;
6347
6347
  function setConfigHash(hash) {
6348
6348
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -10084,4 +10084,4 @@ export {
10084
10084
  managerInstallSystemUnitCommand,
10085
10085
  managerUninstallSystemUnitCommand
10086
10086
  };
10087
- //# sourceMappingURL=chunk-P7UIFZQO.js.map
10087
+ //# sourceMappingURL=chunk-S5KJIVSA.js.map
@@ -16,7 +16,7 @@ import {
16
16
  rotateDailySession,
17
17
  sessionFileExists,
18
18
  todayLocalIso
19
- } from "./chunk-FYZOD3SS.js";
19
+ } from "./chunk-HXGW24VQ.js";
20
20
  import {
21
21
  reapOrphanChannelMcps
22
22
  } from "./chunk-XWVM4KPK.js";
@@ -4322,4 +4322,4 @@ export {
4322
4322
  stopAllSessionsAndWait,
4323
4323
  getProjectDir
4324
4324
  };
4325
- //# sourceMappingURL=chunk-EX5XJEGF.js.map
4325
+ //# sourceMappingURL=chunk-Y34UA33A.js.map
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-SQWO4TKG.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-D4JOMLCY.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-XSUQMCIQ.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-IW4FGSBB.js.map
@@ -55,7 +55,7 @@ import {
55
55
  safeWriteJsonAtomic,
56
56
  setConfigHash,
57
57
  tripClass
58
- } from "../chunk-P7UIFZQO.js";
58
+ } from "../chunk-S5KJIVSA.js";
59
59
  import {
60
60
  getProjectDir as getProjectDir2,
61
61
  getReadyTasks,
@@ -111,7 +111,7 @@ import {
111
111
  takeZombieDetection,
112
112
  toOpencodeModel,
113
113
  writeEgressAllowlist
114
- } from "../chunk-EX5XJEGF.js";
114
+ } from "../chunk-Y34UA33A.js";
115
115
  import {
116
116
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
117
117
  AnchorSessionClient,
@@ -192,14 +192,14 @@ import {
192
192
  subagentActivityAgeSeconds,
193
193
  sumTranscriptUsageInWindow,
194
194
  transcriptActivityAgeSeconds
195
- } from "../chunk-FYZOD3SS.js";
195
+ } from "../chunk-HXGW24VQ.js";
196
196
  import {
197
197
  reapOrphanChannelMcps
198
198
  } from "../chunk-XWVM4KPK.js";
199
199
 
200
200
  // src/lib/manager-worker.ts
201
201
  import { createHash as createHash17 } from "crypto";
202
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync15, rmSync as rmSync5, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
202
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync15, rmSync as rmSync5, readdirSync as readdirSync9, statSync as statSync8, unlinkSync as unlinkSync6, renameSync as renameSync8, utimesSync } from "fs";
203
203
 
204
204
  // src/lib/atomic-file-replace.ts
205
205
  import { copyFileSync, renameSync, unlinkSync } from "fs";
@@ -11611,7 +11611,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
11611
11611
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
11612
11612
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
11613
11613
  async function channelInboundActivityAgeSecondsFor(codeName) {
11614
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-RWMERSER.js");
11614
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-WANP5PYP.js");
11615
11615
  const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
11616
11616
  if (newest === null) return null;
11617
11617
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -12256,7 +12256,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
12256
12256
  var lastVersionCheckAt = 0;
12257
12257
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
12258
12258
  var lastResponsivenessProbeAt = 0;
12259
- var agtCliVersion = true ? "0.28.666" : "dev";
12259
+ var agtCliVersion = true ? "0.28.667" : "dev";
12260
12260
  function resolveBrewPath(execFileSync3) {
12261
12261
  try {
12262
12262
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -13620,7 +13620,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
13620
13620
  if (codeNames.length === 0) return;
13621
13621
  void (async () => {
13622
13622
  try {
13623
- const { collectDiagnostics } = await import("../persistent-session-SQWO4TKG.js");
13623
+ const { collectDiagnostics } = await import("../persistent-session-D4JOMLCY.js");
13624
13624
  await api.post("/host/heartbeat", {
13625
13625
  host_id: hostId,
13626
13626
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -13736,7 +13736,7 @@ async function pollCycleInner() {
13736
13736
  }
13737
13737
  try {
13738
13738
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
13739
- const { collectDiagnostics } = await import("../persistent-session-SQWO4TKG.js");
13739
+ const { collectDiagnostics } = await import("../persistent-session-D4JOMLCY.js");
13740
13740
  const diagCodeNames = [...agentState.persistentSessionAgents];
13741
13741
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor) : void 0;
13742
13742
  let tailscaleHostname;
@@ -13888,7 +13888,7 @@ async function pollCycleInner() {
13888
13888
  collectPanelessActivityProbes,
13889
13889
  getResponsivenessIntervalMs,
13890
13890
  occupancyQualificationClassifications
13891
- } = await import("../responsiveness-probe-RWMERSER.js");
13891
+ } = await import("../responsiveness-probe-WANP5PYP.js");
13892
13892
  const probeIntervalMs = getResponsivenessIntervalMs();
13893
13893
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
13894
13894
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -13998,7 +13998,7 @@ async function pollCycleInner() {
13998
13998
  collectResponsivenessProbes,
13999
13999
  livePendingInboundOldestAgeSeconds,
14000
14000
  parkPendingInbound
14001
- } = await import("../responsiveness-probe-RWMERSER.js");
14001
+ } = await import("../responsiveness-probe-WANP5PYP.js");
14002
14002
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
14003
14003
  const wedgeNow = /* @__PURE__ */ new Date();
14004
14004
  const liveAgents = agentState.persistentSessionAgents;
@@ -14154,7 +14154,7 @@ async function pollCycleInner() {
14154
14154
  }
14155
14155
  try {
14156
14156
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
14157
- const { probeSessionAuth } = await import("../session-auth-dead-OOWR5BPI.js");
14157
+ const { probeSessionAuth } = await import("../session-auth-dead-2L7FWRLT.js");
14158
14158
  const observations = [];
14159
14159
  const pendingCacheCommits = [];
14160
14160
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -17891,7 +17891,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
17891
17891
  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}`));
17892
17892
  void (async () => {
17893
17893
  try {
17894
- const { collectDiagnostics } = await import("../persistent-session-SQWO4TKG.js");
17894
+ const { collectDiagnostics } = await import("../persistent-session-D4JOMLCY.js");
17895
17895
  await api.post("/host/heartbeat", {
17896
17896
  host_id: hostId,
17897
17897
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -17942,7 +17942,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
17942
17942
  }
17943
17943
  try {
17944
17944
  const hostId = await getHostId();
17945
- const { collectDiagnostics } = await import("../persistent-session-SQWO4TKG.js");
17945
+ const { collectDiagnostics } = await import("../persistent-session-D4JOMLCY.js");
17946
17946
  await api.post("/host/heartbeat", {
17947
17947
  host_id: hostId,
17948
17948
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -18573,7 +18573,7 @@ async function processClaudePairSessions(agents) {
18573
18573
  killPairSession,
18574
18574
  pairTmuxSession,
18575
18575
  finalizeClaudePairOnboarding
18576
- } = await import("../claude-pair-runtime-XSUQMCIQ.js");
18576
+ } = await import("../claude-pair-runtime-IW4FGSBB.js");
18577
18577
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
18578
18578
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
18579
18579
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -18796,6 +18796,68 @@ function generateArtifacts(agent, refreshData, adapter, renderIntegrationsSectio
18796
18796
  var memoryFileHashes = /* @__PURE__ */ new Map();
18797
18797
  var lastDownloadHash = /* @__PURE__ */ new Map();
18798
18798
  var lastLocalFileHash = /* @__PURE__ */ new Map();
18799
+ var memoryManifests = /* @__PURE__ */ new Map();
18800
+ function memoryRetirementDir(configDir, codeName) {
18801
+ return join36(configDir, "_memory-retirement", codeName);
18802
+ }
18803
+ function memoryManifestPath(configDir, codeName) {
18804
+ return join36(memoryRetirementDir(configDir, codeName), "manifest.json");
18805
+ }
18806
+ function loadMemoryManifest(agentId, configDir, codeName) {
18807
+ const cached = memoryManifests.get(agentId);
18808
+ if (cached) return cached;
18809
+ let manifest = {};
18810
+ try {
18811
+ const raw = readFileSync28(memoryManifestPath(configDir, codeName), "utf-8");
18812
+ const parsed = JSON.parse(raw);
18813
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
18814
+ for (const [name, entry] of Object.entries(parsed)) {
18815
+ if (entry && typeof entry === "object" && typeof entry.file === "string" && typeof entry.sha === "string") {
18816
+ manifest[name] = { file: entry.file, sha: entry.sha };
18817
+ }
18818
+ }
18819
+ }
18820
+ } catch {
18821
+ manifest = {};
18822
+ }
18823
+ memoryManifests.set(agentId, manifest);
18824
+ return manifest;
18825
+ }
18826
+ function saveMemoryManifest(agentId, configDir, codeName) {
18827
+ const manifest = memoryManifests.get(agentId);
18828
+ if (!manifest) return;
18829
+ try {
18830
+ const path = memoryManifestPath(configDir, codeName);
18831
+ mkdirSync11(memoryRetirementDir(configDir, codeName), { recursive: true });
18832
+ const tmp = `${path}.tmp`;
18833
+ writeFileSync14(tmp, JSON.stringify(manifest, null, 2));
18834
+ renameSync8(tmp, path);
18835
+ } catch {
18836
+ }
18837
+ }
18838
+ function retiredMemoryDir(configDir, codeName) {
18839
+ return join36(memoryRetirementDir(configDir, codeName), "retired");
18840
+ }
18841
+ var RETIRED_MEMORY_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
18842
+ var lastRetirementReport = /* @__PURE__ */ new Map();
18843
+ async function reportMemoryRetirement(agent, outcomes, log2) {
18844
+ const fingerprint3 = outcomes.unapplied.map((u) => `${u.reason}:${u.name}`).sort().join("\0");
18845
+ if (lastRetirementReport.get(agent.agent_id) === fingerprint3) return;
18846
+ try {
18847
+ await api.post("/host/memory-retirement-report", {
18848
+ agent_id: agent.agent_id,
18849
+ unapplied: outcomes.unapplied
18850
+ });
18851
+ lastRetirementReport.set(agent.agent_id, fingerprint3);
18852
+ if (outcomes.unapplied.length > 0) {
18853
+ log2(
18854
+ `[memory-retire] ${agent.code_name}: reported ${outcomes.unapplied.length} retired memory file(s) still present on this host`
18855
+ );
18856
+ }
18857
+ } catch (err) {
18858
+ log2(`[memory-retire] ${agent.code_name}: retirement report failed: ${err.message}`);
18859
+ }
18860
+ }
18799
18861
  var pendingFreshMemorySync = /* @__PURE__ */ new Set();
18800
18862
  function markAgentForFreshMemorySync(agentId) {
18801
18863
  pendingFreshMemorySync.add(agentId);
@@ -18826,17 +18888,131 @@ function parseMemoryFile(raw, fallbackName) {
18826
18888
  type: isDaily ? "daily" : "project"
18827
18889
  };
18828
18890
  }
18891
+ function applyMemoryTombstones(opts) {
18892
+ const { agent, configDir, memoryDir, tombstones, truncated, enabled, log: log2 } = opts;
18893
+ const outcomes = {
18894
+ removed: 0,
18895
+ kept_unknown: 0,
18896
+ kept_modified: 0,
18897
+ already_gone: 0,
18898
+ failed: 0,
18899
+ skipped_disabled: 0,
18900
+ unapplied: []
18901
+ };
18902
+ if (tombstones.length === 0) return outcomes;
18903
+ if (!enabled) {
18904
+ outcomes.skipped_disabled = tombstones.length;
18905
+ return outcomes;
18906
+ }
18907
+ const manifest = loadMemoryManifest(agent.agent_id, configDir, agent.code_name);
18908
+ let manifestDirty = false;
18909
+ for (const tomb of tombstones) {
18910
+ const entry = manifest[tomb.name];
18911
+ if (!entry) {
18912
+ outcomes.kept_unknown++;
18913
+ outcomes.unapplied.push({ name: tomb.name, reason: "kept_unknown" });
18914
+ log2(`[memory-retire] ${agent.code_name}: KEPT '${tomb.name}' \u2014 no write record, not ours to remove`);
18915
+ continue;
18916
+ }
18917
+ const filePath = join36(memoryDir, entry.file);
18918
+ if (!existsSync15(filePath)) {
18919
+ outcomes.already_gone++;
18920
+ delete manifest[tomb.name];
18921
+ manifestDirty = true;
18922
+ continue;
18923
+ }
18924
+ try {
18925
+ const actual = createHash17("sha256").update(readFileSync28(filePath)).digest("hex");
18926
+ if (actual !== entry.sha) {
18927
+ outcomes.kept_modified++;
18928
+ outcomes.unapplied.push({ name: tomb.name, reason: "kept_modified" });
18929
+ log2(
18930
+ `[memory-retire] ${agent.code_name}: KEPT '${tomb.name}' (${entry.file}) \u2014 edited since we wrote it (recorded ${entry.sha.slice(0, 12)}, on disk ${actual.slice(0, 12)})`
18931
+ );
18932
+ continue;
18933
+ }
18934
+ const destDir = retiredMemoryDir(configDir, agent.code_name);
18935
+ mkdirSync11(destDir, { recursive: true });
18936
+ const stamp = Date.now();
18937
+ let dest = join36(destDir, `${entry.file}.${stamp}.retired`);
18938
+ for (let n = 1; existsSync15(dest); n++) {
18939
+ dest = join36(destDir, `${entry.file}.${stamp}-${n}.retired`);
18940
+ }
18941
+ renameSync8(filePath, dest);
18942
+ try {
18943
+ const now = Date.now() / 1e3;
18944
+ utimesSync(dest, now, now);
18945
+ } catch {
18946
+ }
18947
+ delete manifest[tomb.name];
18948
+ manifestDirty = true;
18949
+ memoryFileHashes.get(agent.agent_id)?.delete(entry.file);
18950
+ outcomes.removed++;
18951
+ log2(`[memory-retire] ${agent.code_name}: removed '${tomb.name}' (${entry.file})`);
18952
+ } catch (err) {
18953
+ outcomes.failed++;
18954
+ outcomes.unapplied.push({ name: tomb.name, reason: "failed" });
18955
+ log2(`[memory-retire] ${agent.code_name}: FAILED '${tomb.name}': ${err.message}`);
18956
+ }
18957
+ }
18958
+ if (manifestDirty) saveMemoryManifest(agent.agent_id, configDir, agent.code_name);
18959
+ log2(
18960
+ `[memory-retire] ${agent.code_name}: ${tombstones.length} tombstone(s) \u2014 ${outcomes.removed} removed, ${outcomes.kept_modified} kept (edited), ${outcomes.kept_unknown} kept (not ours), ${outcomes.already_gone} already gone, ${outcomes.failed} failed`
18961
+ );
18962
+ if (truncated) {
18963
+ log2(
18964
+ `[memory-retire] ${agent.code_name}: tombstone list was TRUNCATED by the server \u2014 older retirements are permanently absent from it and their files cannot be reached`
18965
+ );
18966
+ }
18967
+ reapRetiredMemories(configDir, agent.code_name, log2);
18968
+ return outcomes;
18969
+ }
18970
+ function reapRetiredMemories(configDir, codeName, log2) {
18971
+ const dir = retiredMemoryDir(configDir, codeName);
18972
+ if (!existsSync15(dir)) return;
18973
+ const cutoff = Date.now() - RETIRED_MEMORY_TTL_MS;
18974
+ let reaped = 0;
18975
+ try {
18976
+ for (const file of readdirSync9(dir)) {
18977
+ if (!file.endsWith(".retired")) continue;
18978
+ const path = join36(dir, file);
18979
+ try {
18980
+ if (statSync8(path).mtimeMs < cutoff) {
18981
+ unlinkSync6(path);
18982
+ reaped++;
18983
+ }
18984
+ } catch {
18985
+ }
18986
+ }
18987
+ } catch {
18988
+ return;
18989
+ }
18990
+ if (reaped > 0) log2(`[memory-retire] ${codeName}: reaped ${reaped} retired memory file(s) past TTL`);
18991
+ }
18829
18992
  async function syncMemories(agent, configDir, log2) {
18830
18993
  const projectDir = join36(configDir, agent.code_name, "project");
18831
18994
  const memoryDir = join36(projectDir, "memory");
18832
18995
  const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
18833
18996
  if (isFreshSync) {
18834
18997
  log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
18835
- const ok = await downloadMemories(agent, memoryDir, log2, { force: true });
18836
- if (!ok) {
18998
+ const result = await downloadMemories(agent, memoryDir, log2, { force: true, configDir });
18999
+ if (result.status === "failed") {
18837
19000
  log2(`[memory-sync] Fresh-sync download failed for '${agent.code_name}' \u2014 skipping upload, will retry next tick`);
18838
19001
  return;
18839
19002
  }
19003
+ if (result.tombstones) {
19004
+ const tombEnabled = hostFlagStore().getBooleanForAgent("memory-file-tombstones", agent.agent_id);
19005
+ const tombOutcomes = applyMemoryTombstones({
19006
+ agent,
19007
+ configDir,
19008
+ memoryDir,
19009
+ tombstones: result.tombstones,
19010
+ truncated: result.truncated,
19011
+ enabled: tombEnabled,
19012
+ log: log2
19013
+ });
19014
+ if (tombEnabled) await reportMemoryRetirement(agent, tombOutcomes, log2);
19015
+ }
18840
19016
  pendingFreshMemorySync.delete(agent.agent_id);
18841
19017
  }
18842
19018
  if (existsSync15(memoryDir)) {
@@ -18880,10 +19056,23 @@ async function syncMemories(agent, configDir, log2) {
18880
19056
  }
18881
19057
  }
18882
19058
  if (!isFreshSync) {
18883
- await downloadMemories(agent, memoryDir, log2, { force: false });
19059
+ const result = await downloadMemories(agent, memoryDir, log2, { force: false, configDir });
19060
+ if (result.status !== "failed" && result.tombstones) {
19061
+ const tombEnabled = hostFlagStore().getBooleanForAgent("memory-file-tombstones", agent.agent_id);
19062
+ const tombOutcomes = applyMemoryTombstones({
19063
+ agent,
19064
+ configDir,
19065
+ memoryDir,
19066
+ tombstones: result.tombstones,
19067
+ truncated: result.truncated,
19068
+ enabled: tombEnabled,
19069
+ log: log2
19070
+ });
19071
+ if (tombEnabled) await reportMemoryRetirement(agent, tombOutcomes, log2);
19072
+ }
18884
19073
  }
18885
19074
  }
18886
- async function downloadMemories(agent, memoryDir, log2, { force }) {
19075
+ async function downloadMemories(agent, memoryDir, log2, { force, configDir }) {
18887
19076
  const localFiles = existsSync15(memoryDir) ? readdirSync9(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
18888
19077
  const localListHash = createHash17("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
18889
19078
  const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
@@ -18892,21 +19081,24 @@ async function downloadMemories(agent, memoryDir, log2, { force }) {
18892
19081
  const dbMemories = await api.post("/host/memories", {
18893
19082
  agent_id: agent.agent_id
18894
19083
  });
18895
- const responseHash = createHash17("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
19084
+ const tombstones = Array.isArray(dbMemories.tombstones) ? dbMemories.tombstones : null;
19085
+ const responseHash = createHash17("sha256").update(JSON.stringify({ m: dbMemories.memories ?? [], t: tombstones })).digest("hex").slice(0, 16);
18896
19086
  if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
18897
- return true;
19087
+ return { status: "skipped", tombstones, truncated: dbMemories.tombstones_truncated === true };
18898
19088
  }
18899
19089
  lastDownloadHash.set(agent.agent_id, responseHash);
18900
19090
  lastLocalFileHash.set(agent.agent_id, localListHash);
18901
19091
  if (dbMemories.memories?.length) {
18902
19092
  mkdirSync11(memoryDir, { recursive: true });
19093
+ const manifest = loadMemoryManifest(agent.agent_id, configDir, agent.code_name);
18903
19094
  let written = 0;
18904
19095
  let overwritten = 0;
18905
19096
  for (let i = 0; i < dbMemories.memories.length; i++) {
18906
19097
  const mem = dbMemories.memories[i];
18907
19098
  const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
18908
19099
  const slug = rawSlug || `memory-${i}`;
18909
- const filePath = join36(memoryDir, `${slug}.md`);
19100
+ const fileName = `${slug}.md`;
19101
+ const filePath = join36(memoryDir, fileName);
18910
19102
  const desired = `---
18911
19103
  name: ${JSON.stringify(mem.name)}
18912
19104
  type: ${mem.type}
@@ -18915,19 +19107,33 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
18915
19107
 
18916
19108
  ${mem.content}
18917
19109
  `;
19110
+ const record = () => {
19111
+ const sha = createHash17("sha256").update(desired).digest("hex");
19112
+ const prev = manifest[mem.name];
19113
+ if (prev?.file === fileName && prev.sha === sha) return false;
19114
+ manifest[mem.name] = { file: fileName, sha };
19115
+ return true;
19116
+ };
19117
+ let manifestDirty = false;
18918
19118
  if (existsSync15(filePath)) {
18919
19119
  let existing = "";
18920
19120
  try {
18921
19121
  existing = readFileSync28(filePath, "utf-8");
18922
19122
  } catch {
18923
19123
  }
18924
- if (existing === desired) continue;
19124
+ if (existing === desired) {
19125
+ if (record()) manifestDirty = true;
19126
+ if (manifestDirty) saveMemoryManifest(agent.agent_id, configDir, agent.code_name);
19127
+ continue;
19128
+ }
18925
19129
  writeFileSync14(filePath, desired);
18926
19130
  overwritten++;
18927
19131
  } else {
18928
19132
  writeFileSync14(filePath, desired);
18929
19133
  written++;
18930
19134
  }
19135
+ if (record()) manifestDirty = true;
19136
+ if (manifestDirty) saveMemoryManifest(agent.agent_id, configDir, agent.code_name);
18931
19137
  }
18932
19138
  if (written > 0 || overwritten > 0) {
18933
19139
  const updatedFiles = readdirSync9(memoryDir).filter((f) => f.endsWith(".md")).sort();
@@ -18935,10 +19141,10 @@ ${mem.content}
18935
19141
  log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
18936
19142
  }
18937
19143
  }
18938
- return true;
19144
+ return { status: "fetched", tombstones, truncated: dbMemories.tombstones_truncated === true };
18939
19145
  } catch (err) {
18940
19146
  log2(`Memory download failed for '${agent.code_name}': ${err.message}`);
18941
- return false;
19147
+ return { status: "failed", tombstones: null, truncated: false };
18942
19148
  }
18943
19149
  }
18944
19150
  async function cleanupAgentFiles(codeName, agentDir) {
@@ -19550,6 +19756,7 @@ export {
19550
19756
  REALTIME_REBIND_MAX_BACKOFF_MS,
19551
19757
  REALTIME_REBIND_STUCK_AFTER_MS,
19552
19758
  UntrustworthyToolkitResponseError,
19759
+ applyMemoryTombstones,
19553
19760
  applyRestartAcks,
19554
19761
  cancelKanbanNoticeOnDrain,
19555
19762
  channelSyncBackoffMs,
@@ -19578,6 +19785,8 @@ export {
19578
19785
  maybeInjectKanbanCheck,
19579
19786
  maybeProbeClaudeRuntimeAuth,
19580
19787
  maybeUpgradeClaudeCode,
19788
+ memoryManifestPath,
19789
+ memoryManifests,
19581
19790
  pruneRealtimeRebindState,
19582
19791
  realtimeRebindBackoffMs,
19583
19792
  realtimeRebindDue,
@@ -19587,6 +19796,7 @@ export {
19587
19796
  reorderRestartedFirst,
19588
19797
  resolveManagedMcpServerId,
19589
19798
  restartReasonBindsNewMcp,
19799
+ retiredMemoryDir,
19590
19800
  sameChannelSet,
19591
19801
  shouldLogChannelSyncFailure,
19592
19802
  shouldReapOnRetag,