negotium 0.6.7 → 0.6.9

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/registry.js CHANGED
@@ -133,7 +133,7 @@ var codexRegistryOperations = {
133
133
  return { sessionId: threadId, rolloutPath };
134
134
  },
135
135
  async forkSession({ parentSessionId }) {
136
- const { forkCodexSession } = await import("./chunk-f124y9d0.js");
136
+ const { forkCodexSession } = await import("./chunk-bhxdah5c.js");
137
137
  return await forkCodexSession(parentSessionId);
138
138
  },
139
139
  async cleanupRollouts({ sessionIds }) {
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { cpus, freemem, loadavg, totalmem } from "node:os";
2
+ import { cpus, loadavg } from "node:os";
3
3
  import { promisify } from "node:util";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { errMsg } from "#platform/error";
@@ -7,12 +7,34 @@ import { mcpOk } from "../mcp-helpers";
7
7
 
8
8
  const execFileAsync = promisify(execFile);
9
9
 
10
+ export interface SystemHealthMemory {
11
+ /** 앱 실사용 (btop 방식, 회수 불가) = active + wired */
12
+ usedBytes: number;
13
+ /** 파일 캐시/버퍼 등 압박 시 즉시 반환 가능 = inactive + speculative + purgeable */
14
+ cacheBytes: number;
15
+ /** 압축된 익명 메모리 (스왑 압박 시 지표) */
16
+ compressorBytes: number;
17
+ /** available = total - used(active+wired) */
18
+ availableBytes: number;
19
+ totalBytes: number;
20
+ /** active만 (breakdown 표시용) */
21
+ activeBytes: number;
22
+ /** wired만 (breakdown 표시용) */
23
+ wiredBytes: number;
24
+ }
25
+
26
+ export interface SystemHealthMemoryPressure {
27
+ /** normal / warning / critical / unknown */
28
+ level: string;
29
+ /** macOS 공식 "System-wide memory free percentage" (0~100), 없으면 null */
30
+ freePct: number | null;
31
+ }
32
+
10
33
  export interface SystemHealthSnapshot {
11
34
  cpuLoad: [number, number, number];
12
35
  cpuCount: number;
13
- memoryTotal: number;
14
- memoryFree: number;
15
- memoryPressure: string;
36
+ memory: SystemHealthMemory;
37
+ memoryPressure: SystemHealthMemoryPressure;
16
38
  swap: string;
17
39
  disk: string;
18
40
  thermal: string;
@@ -33,13 +55,82 @@ async function shell(command: string, args: string[]): Promise<string> {
33
55
  return stdout.trim();
34
56
  }
35
57
 
36
- async function getMemoryPressure(): Promise<string> {
58
+ async function getMemoryPressure(): Promise<SystemHealthMemoryPressure> {
59
+ // level: 스와핑 압박 레벨 (macOS가 실제 판정하는 공식 지표)
60
+ let level = "unknown";
61
+ try {
62
+ const l = await shell("sysctl", ["-n", "kern.memorystatus_vm_pressure_level"]);
63
+ level = l === "4" ? "critical" : l === "2" ? "warning" : "normal";
64
+ } catch {
65
+ /* keep unknown */
66
+ }
67
+
68
+ // freePct: `memory_pressure` 명령의 "System-wide memory free percentage"
69
+ let freePct: number | null = null;
37
70
  try {
38
- const level = await shell("sysctl", ["-n", "kern.memorystatus_vm_pressure_level"]);
39
- return level === "4" ? "critical" : level === "2" ? "warning" : "normal";
71
+ const out = await shell("memory_pressure", []);
72
+ const m = out.match(/System-wide memory free percentage:\s*(\d+)%/);
73
+ if (m) freePct = Number(m[1]);
40
74
  } catch {
41
- return "unknown";
75
+ /* keep null */
42
76
  }
77
+
78
+ return { level, freePct };
79
+ }
80
+
81
+ async function getTotalMemBytes(): Promise<number> {
82
+ try {
83
+ return Number(await shell("sysctl", ["-n", "hw.memsize"]));
84
+ } catch {
85
+ return 0;
86
+ }
87
+ }
88
+
89
+ async function getVmStatMemory(): Promise<SystemHealthMemory> {
90
+ const [out, totalFromSysctl] = await Promise.all([shell("vm_stat", []), getTotalMemBytes()]);
91
+ const extract = (label: string): number => {
92
+ const m = out.match(new RegExp(`${label.replace(/\s/g, "\\s")}:\\s+(\\d+)`));
93
+ return m ? Number(m[1]) : 0;
94
+ };
95
+
96
+ // page size를 헤더에서 동적 추출 (Apple Silicon 16384 / Intel 4096)
97
+ const pageSize = Number(out.match(/page size of (\d+) bytes/)?.[1]) || 16384;
98
+ const pagesFree = extract("Pages free");
99
+ const pagesActive = extract("Pages active");
100
+ const pagesInactive = extract("Pages inactive");
101
+ const pagesSpeculative = extract("Pages speculative");
102
+ const pagesWired = extract("Pages wired down");
103
+ const pagesPurgeable = extract("Pages purgeable");
104
+ const pagesCompressor = extract("Pages occupied by compressor");
105
+
106
+ // btop 공식: used = (active + wired). inactive/compressor/purgeable 제외.
107
+ const activeBytes = pagesActive * pageSize;
108
+ const wiredBytes = pagesWired * pageSize;
109
+ const usedBytes = activeBytes + wiredBytes;
110
+ const cacheBytes = (pagesInactive + pagesSpeculative + pagesPurgeable) * pageSize;
111
+ const compressorBytes = pagesCompressor * pageSize;
112
+ // total은 hw.memsize 우선 (page 합산은 부정확). 실패 시 page 합산 폴백.
113
+ const totalBytes =
114
+ totalFromSysctl ||
115
+ (pagesFree +
116
+ pagesActive +
117
+ pagesInactive +
118
+ pagesSpeculative +
119
+ pagesWired +
120
+ pagesPurgeable +
121
+ pagesCompressor) *
122
+ pageSize;
123
+ const availableBytes = Math.max(0, totalBytes - usedBytes);
124
+
125
+ return {
126
+ usedBytes,
127
+ cacheBytes,
128
+ compressorBytes,
129
+ availableBytes,
130
+ totalBytes,
131
+ activeBytes,
132
+ wiredBytes,
133
+ };
43
134
  }
44
135
 
45
136
  async function getSwapUsage(): Promise<string> {
@@ -95,19 +186,19 @@ async function getDisk(): Promise<string> {
95
186
 
96
187
  export const defaultSystemHealthMcpHost: SystemHealthMcpHost = {
97
188
  async readSystemHealth() {
98
- const [memoryPressure, swap, thermal, disk, processCount] = await Promise.all([
189
+ const [memoryPressure, swap, thermal, disk, processCount, memory] = await Promise.all([
99
190
  getMemoryPressure(),
100
191
  getSwapUsage(),
101
192
  getThermal(),
102
193
  getDisk(),
103
194
  getProcessCount(),
195
+ getVmStatMemory(),
104
196
  ]);
105
197
  const load = loadavg();
106
198
  return {
107
199
  cpuLoad: [load[0] ?? 0, load[1] ?? 0, load[2] ?? 0],
108
200
  cpuCount: cpus().length,
109
- memoryTotal: totalmem(),
110
- memoryFree: freemem(),
201
+ memory,
111
202
  memoryPressure,
112
203
  swap,
113
204
  disk,
@@ -126,25 +217,36 @@ export function createSystemHealthMcpServer(
126
217
  [
127
218
  "시스템 상태를 조회합니다.",
128
219
  "- CPU 부하 (load average 1/5/15분)",
129
- "- 메모리 사용량 + 압력 (normal/warning/critical)",
220
+ "- 메모리: 앱 실사용(active+wired) / 파일캐시(반환가능) / available / 압력(free%·level)",
130
221
  "- 스왑 사용량",
131
222
  "- 디스크 사용률 (/)",
132
223
  "- 열 상태 (thermal)",
133
224
  "- 프로세스 수",
225
+ "",
226
+ "⚠️ Chromium/playwright 등 무거운 프로세스 실행 가능 여부는 '메모리 사용%'가 아니라",
227
+ " 메모리 압력 레벨(normal/warning/critical) + available GB 로 판단할 것.",
228
+ " (파일 캐시는 압박 시 즉시 반환되므로 used%에 겁먹지 말 것)",
134
229
  ].join("\n"),
135
230
  {},
136
231
  async () => {
137
232
  const snapshot = await host.readSystemHealth();
138
- const used = snapshot.memoryTotal - snapshot.memoryFree;
139
- const memoryPercent = ((used / snapshot.memoryTotal) * 100).toFixed(1);
233
+ const { memory } = snapshot;
234
+ const usedPct =
235
+ memory.totalBytes > 0 ? ((memory.usedBytes / memory.totalBytes) * 100).toFixed(1) : "0.0";
140
236
  const [load1, load5, load15] = snapshot.cpuLoad;
237
+ const pressureStr =
238
+ snapshot.memoryPressure.freePct !== null
239
+ ? `${snapshot.memoryPressure.level} (free ${snapshot.memoryPressure.freePct}%)`
240
+ : snapshot.memoryPressure.level;
141
241
  return mcpOk(
142
242
  [
143
243
  "시스템 상태",
144
244
  "",
145
245
  `CPU 부하: ${load1.toFixed(2)} / ${load5.toFixed(2)} / ${load15.toFixed(2)} (1/5/15분, 코어 ${snapshot.cpuCount}개)`,
146
- `메모리: ${fmtBytes(used)} / ${fmtBytes(snapshot.memoryTotal)} (${memoryPercent}%) 여유: ${fmtBytes(snapshot.memoryFree)}`,
147
- `메모리 압력: ${snapshot.memoryPressure}`,
246
+ `메모리 실사용: ${fmtBytes(memory.usedBytes)} / ${fmtBytes(memory.totalBytes)} (${usedPct}%) available: ${fmtBytes(memory.availableBytes)}`,
247
+ ` (active ${fmtBytes(memory.activeBytes)} + wired ${fmtBytes(memory.wiredBytes)})`,
248
+ `파일 캐시: ${fmtBytes(memory.cacheBytes)} (압박 시 반환 가능) | 압축 ${fmtBytes(memory.compressorBytes)}`,
249
+ `메모리 압력: ${pressureStr}`,
148
250
  `스왑: ${snapshot.swap}`,
149
251
  `디스크(/): ${snapshot.disk}`,
150
252
  `열 상태: ${snapshot.thermal}`,
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.6.7";
1
+ export const NEGOTIUM_VERSION = "0.6.9";
@@ -1,10 +1,30 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export interface SystemHealthMemory {
3
+ /** 앱 실사용 (btop 방식, 회수 불가) = active + wired */
4
+ usedBytes: number;
5
+ /** 파일 캐시/버퍼 등 압박 시 즉시 반환 가능 = inactive + speculative + purgeable */
6
+ cacheBytes: number;
7
+ /** 압축된 익명 메모리 (스왑 압박 시 지표) */
8
+ compressorBytes: number;
9
+ /** available = total - used(active+wired) */
10
+ availableBytes: number;
11
+ totalBytes: number;
12
+ /** active만 (breakdown 표시용) */
13
+ activeBytes: number;
14
+ /** wired만 (breakdown 표시용) */
15
+ wiredBytes: number;
16
+ }
17
+ export interface SystemHealthMemoryPressure {
18
+ /** normal / warning / critical / unknown */
19
+ level: string;
20
+ /** macOS 공식 "System-wide memory free percentage" (0~100), 없으면 null */
21
+ freePct: number | null;
22
+ }
2
23
  export interface SystemHealthSnapshot {
3
24
  cpuLoad: [number, number, number];
4
25
  cpuCount: number;
5
- memoryTotal: number;
6
- memoryFree: number;
7
- memoryPressure: string;
26
+ memory: SystemHealthMemory;
27
+ memoryPressure: SystemHealthMemoryPressure;
8
28
  swap: string;
9
29
  disk: string;
10
30
  thermal: string;
@@ -1 +1 @@
1
- export declare const NEGOTIUM_VERSION = "0.6.7";
1
+ export declare const NEGOTIUM_VERSION = "0.6.9";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "negotium",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "type": "module",
5
5
  "description": "Install the Negotium multi-agent runtime and CLI with one package",
6
6
  "license": "Apache-2.0",