@cleocode/adapters 2026.6.14 → 2026.6.15

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/index.js CHANGED
@@ -42786,6 +42786,188 @@ var init_install = __esm({
42786
42786
  }
42787
42787
  });
42788
42788
 
42789
+ // packages/adapters/src/providers/shared/agent-spawn-wrapper.ts
42790
+ import { spawnSync } from "node:child_process";
42791
+ import { readFileSync as readFileSync6 } from "node:fs";
42792
+ function hasSystemdRun() {
42793
+ if (_systemdRunAvailable !== void 0) return _systemdRunAvailable;
42794
+ if (process.platform !== "linux") {
42795
+ _systemdRunAvailable = false;
42796
+ return false;
42797
+ }
42798
+ const probe = spawnSync("systemd-run", ["--version"], { stdio: "ignore" });
42799
+ _systemdRunAvailable = probe.status === 0;
42800
+ return _systemdRunAvailable;
42801
+ }
42802
+ function readMemTotalBytes() {
42803
+ if (process.platform !== "linux") return 32 * 1024 * 1024 * 1024;
42804
+ try {
42805
+ const raw = readFileSync6("/proc/meminfo", "utf8");
42806
+ const m = raw.match(/^MemTotal:\s+(\d+)\s+kB/m);
42807
+ if (m?.[1]) return parseInt(m[1], 10) * 1024;
42808
+ } catch {
42809
+ }
42810
+ return 32 * 1024 * 1024 * 1024;
42811
+ }
42812
+ function buildAgentSpawnArgs(command, args, scopeId) {
42813
+ if (!hasSystemdRun()) {
42814
+ if (!_demotionLogged) {
42815
+ _demotionLogged = true;
42816
+ process.stderr.write(
42817
+ "[cleo:agent-spawn-wrapper] systemd-run unavailable \u2014 falling back to plain pgid (detached) spawn; no cgroup containment\n"
42818
+ );
42819
+ }
42820
+ return {
42821
+ command: "sh",
42822
+ args: ["-c", 'ulimit -c 0; exec "$@"', "sh", command, ...args],
42823
+ ownership: {
42824
+ mode: "pgid"
42825
+ // pgid is populated after spawn; the caller patches it via the
42826
+ // returned child.pid (which is the pgid leader when detached: true).
42827
+ }
42828
+ };
42829
+ }
42830
+ const totalBytes = readMemTotalBytes();
42831
+ const maxStr = DEFAULT_MEMORY_MAX;
42832
+ const counter = ++_scopeCounter;
42833
+ const discriminator = scopeId ? scopeId.replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 40) : String(counter);
42834
+ const unitName = `cleo-agent-session-${discriminator}.scope`;
42835
+ void totalBytes;
42836
+ const wrapArgs = [
42837
+ "--user",
42838
+ "--scope",
42839
+ `--slice=${CLEO_SLICE}`,
42840
+ `--unit=${unitName}`,
42841
+ "-p",
42842
+ `MemoryMax=${maxStr}`,
42843
+ "-p",
42844
+ "MemorySwapMax=0",
42845
+ // NOTE: ManagedOOMPreference=avoid is NOT set for agent sessions —
42846
+ // only daemon/db scope classes (write-txn holders) get 'avoid'.
42847
+ // See spawn-wrapper.ts OOM_AVOID_CLASSES and the module TSDoc for rationale.
42848
+ "--",
42849
+ "sh",
42850
+ "-c",
42851
+ 'ulimit -c 0; exec "$@"',
42852
+ "sh",
42853
+ command,
42854
+ ...args
42855
+ ];
42856
+ return {
42857
+ command: "systemd-run",
42858
+ args: wrapArgs,
42859
+ ownership: {
42860
+ mode: "systemd",
42861
+ unitName
42862
+ // pgid is populated after spawn; caller patches via child.pid.
42863
+ }
42864
+ };
42865
+ }
42866
+ var CLEO_SLICE, DEFAULT_MEMORY_MAX, _systemdRunAvailable, _demotionLogged, _scopeCounter;
42867
+ var init_agent_spawn_wrapper = __esm({
42868
+ "packages/adapters/src/providers/shared/agent-spawn-wrapper.ts"() {
42869
+ "use strict";
42870
+ CLEO_SLICE = "cleo.slice";
42871
+ DEFAULT_MEMORY_MAX = "32G";
42872
+ _demotionLogged = false;
42873
+ _scopeCounter = 0;
42874
+ }
42875
+ });
42876
+
42877
+ // packages/adapters/src/providers/claude-code/suite-reaper.ts
42878
+ import { spawnSync as spawnSync2 } from "node:child_process";
42879
+ async function reapAgentSuite(ownership) {
42880
+ switch (ownership.mode) {
42881
+ case "systemd":
42882
+ await reapSystemdScope(ownership);
42883
+ break;
42884
+ case "pgid":
42885
+ await reapPgidGroup(ownership);
42886
+ break;
42887
+ case "none":
42888
+ process.stderr.write(
42889
+ "[cleo:suite-reaper] containment mode=none \u2014 no reap performed; janitor (T11995) is the backstop\n"
42890
+ );
42891
+ break;
42892
+ default: {
42893
+ const _exhaustive = ownership.mode;
42894
+ void _exhaustive;
42895
+ }
42896
+ }
42897
+ }
42898
+ async function reapSystemdScope(ownership) {
42899
+ const { unitName, pgid } = ownership;
42900
+ if (unitName) {
42901
+ const stopResult = spawnSync2("systemctl", ["--user", "stop", unitName], {
42902
+ stdio: "ignore",
42903
+ timeout: 1e4
42904
+ });
42905
+ if (stopResult.error) {
42906
+ process.stderr.write(
42907
+ `[cleo:suite-reaper] systemctl stop failed (${stopResult.error.message}); falling back to pgid kill
42908
+ `
42909
+ );
42910
+ } else if (stopResult.status !== 0) {
42911
+ spawnSync2("systemctl", ["--user", "reset-failed", unitName], {
42912
+ stdio: "ignore",
42913
+ timeout: 5e3
42914
+ });
42915
+ if (pgid !== void 0) {
42916
+ await killPgidGracefully(pgid);
42917
+ }
42918
+ return;
42919
+ } else {
42920
+ spawnSync2("systemctl", ["--user", "reset-failed", unitName], {
42921
+ stdio: "ignore",
42922
+ timeout: 5e3
42923
+ });
42924
+ return;
42925
+ }
42926
+ }
42927
+ if (pgid !== void 0) {
42928
+ await killPgidGracefully(pgid);
42929
+ }
42930
+ }
42931
+ async function reapPgidGroup(ownership) {
42932
+ const { pgid } = ownership;
42933
+ if (pgid === void 0) return;
42934
+ await killPgidGracefully(pgid);
42935
+ }
42936
+ async function killPgidGracefully(pgid) {
42937
+ try {
42938
+ process.kill(-pgid, "SIGTERM");
42939
+ } catch (err) {
42940
+ if (isEsrch(err)) return;
42941
+ process.stderr.write(`[cleo:suite-reaper] SIGTERM to pgid=${pgid} failed: ${String(err)}
42942
+ `);
42943
+ }
42944
+ await sleep(SIGTERM_GRACE_MS);
42945
+ try {
42946
+ process.kill(-pgid, "SIGKILL");
42947
+ } catch (err) {
42948
+ if (isEsrch(err)) return;
42949
+ process.stderr.write(`[cleo:suite-reaper] SIGKILL to pgid=${pgid} failed: ${String(err)}
42950
+ `);
42951
+ }
42952
+ }
42953
+ function isEsrch(err) {
42954
+ if (typeof err === "object" && err !== null) {
42955
+ const code = err["code"];
42956
+ return code === "ESRCH";
42957
+ }
42958
+ return false;
42959
+ }
42960
+ function sleep(ms) {
42961
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
42962
+ }
42963
+ var SIGTERM_GRACE_MS;
42964
+ var init_suite_reaper = __esm({
42965
+ "packages/adapters/src/providers/claude-code/suite-reaper.ts"() {
42966
+ "use strict";
42967
+ SIGTERM_GRACE_MS = 3e3;
42968
+ }
42969
+ });
42970
+
42789
42971
  // packages/adapters/src/providers/claude-code/spawn.ts
42790
42972
  import { exec, spawn as nodeSpawn } from "node:child_process";
42791
42973
  import { unlink, writeFile } from "node:fs/promises";
@@ -42795,6 +42977,8 @@ var init_spawn2 = __esm({
42795
42977
  "packages/adapters/src/providers/claude-code/spawn.ts"() {
42796
42978
  "use strict";
42797
42979
  init_src();
42980
+ init_agent_spawn_wrapper();
42981
+ init_suite_reaper();
42798
42982
  execAsync = promisify2(exec);
42799
42983
  ClaudeCodeSpawnProvider = class {
42800
42984
  /** Map of instance IDs to tracked process info. */
@@ -42842,15 +43026,17 @@ var init_spawn2 = __esm({
42842
43026
  }
42843
43027
  tmpFile = `/tmp/claude-spawn-${instanceId}.txt`;
42844
43028
  await writeFile(tmpFile, enrichedPrompt, "utf-8");
42845
- const args = [
43029
+ const claudeArgs = [
42846
43030
  "--print",
42847
43031
  "--dangerously-skip-permissions",
42848
43032
  "--output-format",
42849
43033
  "json",
42850
43034
  tmpFile
42851
43035
  ];
43036
+ const spawnBuild = buildAgentSpawnArgs("claude", claudeArgs, instanceId);
43037
+ const isSystemd = spawnBuild.ownership.mode === "systemd";
42852
43038
  const spawnOpts = {
42853
- detached: true,
43039
+ detached: !isSystemd,
42854
43040
  stdio: ["ignore", "pipe", "pipe"]
42855
43041
  };
42856
43042
  if (context.workingDirectory) {
@@ -42860,13 +43046,19 @@ var init_spawn2 = __esm({
42860
43046
  if (optionsEnv !== void 0 && Object.keys(optionsEnv).length > 0) {
42861
43047
  spawnOpts.env = { ...process.env, ...optionsEnv };
42862
43048
  }
42863
- const child = nodeSpawn("claude", args, spawnOpts);
43049
+ const child = nodeSpawn(spawnBuild.command, spawnBuild.args, spawnOpts);
42864
43050
  child.unref();
43051
+ const ownership = {
43052
+ ...spawnBuild.ownership,
43053
+ pid: child.pid,
43054
+ pgid: spawnBuild.ownership.mode === "pgid" && child.pid !== void 0 ? child.pid : spawnBuild.ownership.pgid
43055
+ };
42865
43056
  if (child.pid) {
42866
43057
  this.processMap.set(instanceId, {
42867
43058
  pid: child.pid,
42868
43059
  taskId: context.taskId,
42869
- startTime
43060
+ startTime,
43061
+ ownership
42870
43062
  });
42871
43063
  }
42872
43064
  const capturedTmpFile = tmpFile;
@@ -42882,7 +43074,10 @@ var init_spawn2 = __esm({
42882
43074
  taskId: context.taskId,
42883
43075
  providerId: "claude-code",
42884
43076
  status: "running",
42885
- startTime
43077
+ startTime,
43078
+ // T11998: surface the ownership handle in the result so callers can
43079
+ // persist it or pass it to reapAgentSuite on session end.
43080
+ ownership
42886
43081
  };
42887
43082
  } catch (error48) {
42888
43083
  console.error(`[ClaudeCodeSpawnProvider] Failed to spawn: ${getErrorMessage(error48)}`);
@@ -42921,7 +43116,9 @@ var init_spawn2 = __esm({
42921
43116
  taskId: tracked.taskId,
42922
43117
  providerId: "claude-code",
42923
43118
  status: "running",
42924
- startTime: tracked.startTime
43119
+ startTime: tracked.startTime,
43120
+ // T11998: propagate ownership handle so callers can reap the suite.
43121
+ ownership: tracked.ownership
42925
43122
  });
42926
43123
  } catch {
42927
43124
  this.processMap.delete(instanceId);
@@ -42932,19 +43129,40 @@ var init_spawn2 = __esm({
42932
43129
  /**
42933
43130
  * Terminate a running spawn by instance ID.
42934
43131
  *
42935
- * Sends SIGTERM to the tracked process. If the process is not found
42936
- * or has already exited, this is a no-op.
43132
+ * Uses the suite-reaper to kill the entire process tree (root claude CLI +
43133
+ * all MCP grandchildren) via the containment handle recorded at spawn time.
43134
+ * Falls back to a direct SIGTERM on the tracked PID for legacy entries
43135
+ * that pre-date T11998 and lack an ownership handle.
43136
+ *
43137
+ * Idempotent: no-op if the instance is not found or has already exited.
42937
43138
  *
42938
43139
  * @param instanceId - ID of the spawn instance to terminate
43140
+ * @task T11998
42939
43141
  */
42940
43142
  async terminate(instanceId) {
42941
43143
  const tracked = this.processMap.get(instanceId);
42942
43144
  if (!tracked) return;
43145
+ this.processMap.delete(instanceId);
42943
43146
  try {
42944
- process.kill(tracked.pid, "SIGTERM");
43147
+ await reapAgentSuite(tracked.ownership);
42945
43148
  } catch {
43149
+ try {
43150
+ process.kill(tracked.pid, "SIGTERM");
43151
+ } catch {
43152
+ }
42946
43153
  }
42947
- this.processMap.delete(instanceId);
43154
+ }
43155
+ /**
43156
+ * Terminate all tracked spawn instances on session end.
43157
+ *
43158
+ * Called by the session lifecycle when a CLEO session ends, ensuring
43159
+ * no orphaned agent suites (root process + MCP children) remain.
43160
+ *
43161
+ * @task T11998
43162
+ */
43163
+ async terminateAll() {
43164
+ const instanceIds = [...this.processMap.keys()];
43165
+ await Promise.allSettled(instanceIds.map((id) => this.terminate(id)));
42948
43166
  }
42949
43167
  };
42950
43168
  }
@@ -43234,7 +43452,7 @@ var init_adapter = __esm({
43234
43452
  });
43235
43453
 
43236
43454
  // packages/adapters/src/providers/claude-code/statusline.ts
43237
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
43455
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
43238
43456
  import { homedir as homedir8 } from "node:os";
43239
43457
  import { join as join10 } from "node:path";
43240
43458
  function getClaudeSettingsPath() {
@@ -43244,7 +43462,7 @@ function checkStatuslineIntegration() {
43244
43462
  const settingsPath = getClaudeSettingsPath();
43245
43463
  if (!existsSync8(settingsPath)) return "no_settings";
43246
43464
  try {
43247
- const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
43465
+ const settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
43248
43466
  const statusLine = settings.statusLine;
43249
43467
  if (!statusLine?.type) return "not_configured";
43250
43468
  if (statusLine.type !== "command") return "custom_no_cleo";
@@ -43255,7 +43473,7 @@ function checkStatuslineIntegration() {
43255
43473
  const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir8()) : cmd;
43256
43474
  if (existsSync8(scriptPath)) {
43257
43475
  try {
43258
- const content = readFileSync6(scriptPath, "utf-8");
43476
+ const content = readFileSync7(scriptPath, "utf-8");
43259
43477
  if (content.includes("context-state.json")) return "configured";
43260
43478
  } catch {
43261
43479
  }
@@ -43488,7 +43706,7 @@ var init_hooks2 = __esm({
43488
43706
  });
43489
43707
 
43490
43708
  // packages/adapters/src/providers/cursor/install.ts
43491
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
43709
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
43492
43710
  import { join as join16 } from "node:path";
43493
43711
  import { ensureProviderInstructionFile as ensureProviderInstructionFile3 } from "@cleocode/caamp";
43494
43712
  var CursorInstallProvider;
@@ -43546,7 +43764,7 @@ var init_install2 = __esm({
43546
43764
  const rulesPath = join16(process.cwd(), ".cursorrules");
43547
43765
  if (existsSync11(rulesPath)) {
43548
43766
  try {
43549
- const content = readFileSync7(rulesPath, "utf-8");
43767
+ const content = readFileSync8(rulesPath, "utf-8");
43550
43768
  const injectionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
43551
43769
  if (content.includes(injectionRef) || content.includes("@.cleo/memory-bridge.md")) {
43552
43770
  return true;
@@ -43599,7 +43817,7 @@ var init_install2 = __esm({
43599
43817
  if (!existsSync11(rulesPath)) {
43600
43818
  return false;
43601
43819
  }
43602
- let content = readFileSync7(rulesPath, "utf-8");
43820
+ let content = readFileSync8(rulesPath, "utf-8");
43603
43821
  const cursorRefs = [
43604
43822
  `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
43605
43823
  "@.cleo/memory-bridge.md"
@@ -43640,7 +43858,7 @@ var init_install2 = __esm({
43640
43858
  ""
43641
43859
  ].join("\n");
43642
43860
  if (existsSync11(mdcPath)) {
43643
- const existing = readFileSync7(mdcPath, "utf-8");
43861
+ const existing = readFileSync8(mdcPath, "utf-8");
43644
43862
  if (existing === expectedContent) {
43645
43863
  return false;
43646
43864
  }
@@ -43716,7 +43934,7 @@ var init_install2 = __esm({
43716
43934
  let config2 = {};
43717
43935
  if (existsSync11(hooksJsonPath)) {
43718
43936
  try {
43719
- config2 = JSON.parse(readFileSync7(hooksJsonPath, "utf-8"));
43937
+ config2 = JSON.parse(readFileSync8(hooksJsonPath, "utf-8"));
43720
43938
  } catch {
43721
43939
  }
43722
43940
  }
@@ -44050,7 +44268,7 @@ var init_hooks3 = __esm({
44050
44268
  });
44051
44269
 
44052
44270
  // packages/adapters/src/providers/opencode/install.ts
44053
- import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
44271
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
44054
44272
  import { join as join23 } from "node:path";
44055
44273
  import { ensureProviderInstructionFile as ensureProviderInstructionFile6 } from "@cleocode/caamp";
44056
44274
  var OpenCodeInstallProvider;
@@ -44214,7 +44432,7 @@ var init_install3 = __esm({
44214
44432
  ].join("\n");
44215
44433
  if (existsSync17(pluginPath)) {
44216
44434
  try {
44217
- if (readFileSync10(pluginPath, "utf-8") === generated) {
44435
+ if (readFileSync11(pluginPath, "utf-8") === generated) {
44218
44436
  return false;
44219
44437
  }
44220
44438
  } catch {
@@ -45769,7 +45987,7 @@ var GeminiCliHookProvider = class {
45769
45987
 
45770
45988
  // packages/adapters/src/providers/gemini-cli/install.ts
45771
45989
  init_paths2();
45772
- import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
45990
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
45773
45991
  import { join as join19 } from "node:path";
45774
45992
  import { ensureProviderInstructionFile as ensureProviderInstructionFile4 } from "@cleocode/caamp";
45775
45993
  var GeminiCliInstallProvider = class {
@@ -45818,7 +46036,7 @@ var GeminiCliInstallProvider = class {
45818
46036
  const geminiMdPath = join19(process.cwd(), "GEMINI.md");
45819
46037
  if (existsSync13(geminiMdPath)) {
45820
46038
  try {
45821
- const content = readFileSync8(geminiMdPath, "utf-8");
46039
+ const content = readFileSync9(geminiMdPath, "utf-8");
45822
46040
  if (content.includes(instructionRef)) {
45823
46041
  return true;
45824
46042
  }
@@ -46041,7 +46259,7 @@ var KimiHookProvider = class {
46041
46259
 
46042
46260
  // packages/adapters/src/providers/kimi/install.ts
46043
46261
  init_paths2();
46044
- import { existsSync as existsSync15, readFileSync as readFileSync9 } from "node:fs";
46262
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
46045
46263
  import { join as join21 } from "node:path";
46046
46264
  import { ensureProviderInstructionFile as ensureProviderInstructionFile5 } from "@cleocode/caamp";
46047
46265
  var KimiInstallProvider = class {
@@ -46090,7 +46308,7 @@ var KimiInstallProvider = class {
46090
46308
  const agentsMdPath = join21(process.cwd(), "AGENTS.md");
46091
46309
  if (existsSync15(agentsMdPath)) {
46092
46310
  try {
46093
- const content = readFileSync9(agentsMdPath, "utf-8");
46311
+ const content = readFileSync10(agentsMdPath, "utf-8");
46094
46312
  if (content.includes(instructionRef)) {
46095
46313
  return true;
46096
46314
  }
@@ -46241,7 +46459,7 @@ init_opencode();
46241
46459
  init_hook_template_installer();
46242
46460
 
46243
46461
  // packages/adapters/src/registry.ts
46244
- import { readFileSync as readFileSync11 } from "node:fs";
46462
+ import { readFileSync as readFileSync12 } from "node:fs";
46245
46463
  import { dirname as dirname4, join as join28, resolve as resolve2 } from "node:path";
46246
46464
  import { fileURLToPath as fileURLToPath3 } from "node:url";
46247
46465
  var PROVIDER_IDS = ["claude-code", "opencode", "cursor", "pi"];
@@ -46251,7 +46469,7 @@ function getProviderManifests() {
46251
46469
  for (const providerId of PROVIDER_IDS) {
46252
46470
  try {
46253
46471
  const manifestPath = join28(baseDir, providerId, "manifest.json");
46254
- const raw = readFileSync11(manifestPath, "utf-8");
46472
+ const raw = readFileSync12(manifestPath, "utf-8");
46255
46473
  manifests.push(JSON.parse(raw));
46256
46474
  } catch {
46257
46475
  }