agent-relay 2.0.6 → 2.0.7
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/deploy/workspace/entrypoint.sh +80 -22
- package/deploy/workspace/gh-credential-relay +90 -0
- package/dist/dashboard/out/404.html +1 -1
- package/{packages/dashboard/ui-dist/_next/static/chunks/677-7323947c23b35979.js → dist/dashboard/out/_next/static/chunks/320-22ebe7be58cf982a.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-9914652442f7e4fb.js +1 -0
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/app/page-2e525b1dcc790967.js → dist/dashboard/out/_next/static/chunks/app/app/page-9d6bc8729b429956.js} +1 -1
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/cloud/link/page-5011ae044b90449d.js → dist/dashboard/out/_next/static/chunks/app/cloud/link/page-fa1d5842aa90e8a6.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-113060009ef35bc2.js +1 -0
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/history/page-b2ce7c96ed0931da.js → dist/dashboard/out/_next/static/chunks/app/history/page-9965d2483011b846.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-6b91e33784c20610.js +1 -0
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/login/page-6ec54eee75877971.js → dist/dashboard/out/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js} +1 -1
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/metrics/page-bf2cb1e5915bc92d.js → dist/dashboard/out/_next/static/chunks/app/metrics/page-1e37ef8e73940b40.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/{page-4e64923d73c35bc9.js → page-487fa38f041815c1.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-9db3ebdfa567a7c9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/{page-84161c802b020a1f.js → page-4dbe33f0f7691b7c.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/{page-18a4665665f6be11.js → page-1ede2205b58649ca.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-fdbeb09028f57c9f.js +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +2 -2
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +2 -2
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +2 -2
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -2
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +2 -2
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +2 -2
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +2 -2
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +2 -2
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +2 -2
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +2 -2
- package/package.json +14 -14
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/dist/spawner.js +9 -0
- package/packages/bridge/package.json +7 -7
- package/packages/cloud/dist/server.js +3 -3
- package/packages/cloud/package.json +6 -6
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +9 -9
- package/packages/dashboard/package.json +12 -12
- package/packages/dashboard/ui/react-components/App.tsx +21 -435
- package/packages/dashboard/ui/react-components/ChannelChat.tsx +29 -75
- package/packages/dashboard/ui/react-components/MessageComposer.tsx +457 -0
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-22ebe7be58cf982a.js +1 -0
- package/{dist/dashboard/out/_next/static/chunks/app/app/page-2e525b1dcc790967.js → packages/dashboard/ui-dist/_next/static/chunks/app/app/page-9d6bc8729b429956.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/{page-4e64923d73c35bc9.js → page-487fa38f041815c1.js} +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +2 -2
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +2 -2
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +2 -2
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +2 -2
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +2 -2
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +2 -2
- package/packages/dashboard/ui-dist/login.html +2 -2
- package/packages/dashboard/ui-dist/login.txt +2 -2
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +2 -2
- package/packages/dashboard/ui-dist/pricing.html +2 -2
- package/packages/dashboard/ui-dist/pricing.txt +2 -2
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +2 -2
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +2 -2
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +2 -2
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +2 -2
- package/packages/dashboard/ui-dist/signup.html +2 -2
- package/packages/dashboard/ui-dist/signup.txt +2 -2
- package/packages/dashboard-server/dist/server.js +5 -5
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/dist/cgroup-manager.d.ts +152 -0
- package/packages/resiliency/dist/cgroup-manager.js +394 -0
- package/packages/resiliency/dist/index.d.ts +1 -0
- package/packages/resiliency/dist/index.js +1 -0
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +8 -4
- package/packages/wrapper/dist/relay-pty-orchestrator.js +47 -25
- package/packages/wrapper/package.json +6 -6
- package/dist/dashboard/out/_next/static/chunks/320-900169c942e31422.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-5011ae044b90449d.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/history/page-b2ce7c96ed0931da.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-6ec54eee75877971.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-bf2cb1e5915bc92d.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-900169c942e31422.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/app/page-44813aa26ad19681.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/page-7993778218818ace.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/setup/[provider]/page-84161c802b020a1f.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-18a4665665f6be11.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +0 -1
- /package/dist/dashboard/out/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{KIxE0Ds_zdGuDJDQu7_sb → 1yt8VDAusp2yTBf4JFA7F}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{KIxE0Ds_zdGuDJDQu7_sb → 1yt8VDAusp2yTBf4JFA7F}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{SoK46dEi3IsNBVWXD9x0L → chdCrViSD2yReZElqRfk0}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{SoK46dEi3IsNBVWXD9x0L → chdCrViSD2yReZElqRfk0}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CgroupManager - Manage CPU limits for agents using Linux cgroups v2
|
|
3
|
+
*
|
|
4
|
+
* Provides per-agent CPU isolation to prevent one agent (e.g., running npm install)
|
|
5
|
+
* from starving other agents of CPU resources.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Auto-detects cgroups v2 availability
|
|
9
|
+
* - Creates per-agent cgroups with CPU limits
|
|
10
|
+
* - Gracefully degrades when cgroups unavailable
|
|
11
|
+
* - Cleans up cgroups when agents exit
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const manager = getCgroupManager();
|
|
16
|
+
* await manager.createAgentCgroup('worker1', { cpuPercent: 50 });
|
|
17
|
+
* await manager.addProcess('worker1', pid);
|
|
18
|
+
* // ... agent runs with CPU limit ...
|
|
19
|
+
* await manager.removeAgentCgroup('worker1');
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Requirements:
|
|
23
|
+
* - Linux with cgroups v2 (unified hierarchy)
|
|
24
|
+
* - Write access to cgroup directory (delegated or root)
|
|
25
|
+
* - cpu controller enabled in cgroup
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync, readdirSync } from 'node:fs';
|
|
28
|
+
import { join } from 'node:path';
|
|
29
|
+
import { EventEmitter } from 'node:events';
|
|
30
|
+
/**
|
|
31
|
+
* Default cgroup base path for agent-relay
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_CGROUP_BASE = '/sys/fs/cgroup/agent-relay';
|
|
34
|
+
/**
|
|
35
|
+
* Default CPU settings
|
|
36
|
+
*/
|
|
37
|
+
const DEFAULT_CPU_PERCENT = 100; // 100% of one core
|
|
38
|
+
const DEFAULT_CPU_PERIOD_US = 100000; // 100ms period
|
|
39
|
+
/**
|
|
40
|
+
* CgroupManager singleton for managing agent CPU limits
|
|
41
|
+
*/
|
|
42
|
+
export class CgroupManager extends EventEmitter {
|
|
43
|
+
cgroupBase;
|
|
44
|
+
available;
|
|
45
|
+
agentCgroups = new Map();
|
|
46
|
+
initialized = false;
|
|
47
|
+
constructor(cgroupBase = DEFAULT_CGROUP_BASE) {
|
|
48
|
+
super();
|
|
49
|
+
this.cgroupBase = cgroupBase;
|
|
50
|
+
this.available = false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Initialize the cgroup manager and detect availability
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
if (this.initialized) {
|
|
57
|
+
return this.available;
|
|
58
|
+
}
|
|
59
|
+
this.initialized = true;
|
|
60
|
+
this.available = await this.detectCgroupsV2();
|
|
61
|
+
if (this.available) {
|
|
62
|
+
// Ensure base directory exists
|
|
63
|
+
try {
|
|
64
|
+
await this.ensureBaseCgroup();
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.warn(`[cgroup-manager] Failed to create base cgroup: ${err.message}`);
|
|
68
|
+
this.available = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return this.available;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if cgroups v2 is available and we have write access
|
|
75
|
+
*/
|
|
76
|
+
async detectCgroupsV2() {
|
|
77
|
+
// Check for cgroups v2 unified hierarchy
|
|
78
|
+
const cgroupRoot = '/sys/fs/cgroup';
|
|
79
|
+
// Check if cgroup2 is mounted (unified hierarchy)
|
|
80
|
+
if (!existsSync(join(cgroupRoot, 'cgroup.controllers'))) {
|
|
81
|
+
console.info('[cgroup-manager] cgroups v2 not detected (no unified hierarchy)');
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// Check if cpu controller is available
|
|
85
|
+
try {
|
|
86
|
+
const controllers = readFileSync(join(cgroupRoot, 'cgroup.controllers'), 'utf-8');
|
|
87
|
+
if (!controllers.includes('cpu')) {
|
|
88
|
+
console.info('[cgroup-manager] CPU controller not available in cgroups');
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.info(`[cgroup-manager] Cannot read cgroup controllers: ${err.message}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Check if we can write to cgroup directory
|
|
97
|
+
// In production, agent-relay cgroup should be pre-created with proper delegation
|
|
98
|
+
try {
|
|
99
|
+
const testPath = join(cgroupRoot, 'agent-relay-test-' + process.pid);
|
|
100
|
+
mkdirSync(testPath, { recursive: true });
|
|
101
|
+
rmSync(testPath, { recursive: true, force: true });
|
|
102
|
+
console.info('[cgroup-manager] cgroups v2 available with write access');
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// Try delegated cgroup path
|
|
107
|
+
if (existsSync(this.cgroupBase)) {
|
|
108
|
+
console.info('[cgroup-manager] Using delegated cgroup at ' + this.cgroupBase);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
console.info(`[cgroup-manager] No write access to cgroups: ${err.message}`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Ensure base cgroup directory exists with proper controllers
|
|
117
|
+
*/
|
|
118
|
+
async ensureBaseCgroup() {
|
|
119
|
+
if (!existsSync(this.cgroupBase)) {
|
|
120
|
+
mkdirSync(this.cgroupBase, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
// Enable cpu controller in subtree
|
|
123
|
+
const subtreeControlPath = join(this.cgroupBase, 'cgroup.subtree_control');
|
|
124
|
+
if (existsSync(subtreeControlPath)) {
|
|
125
|
+
try {
|
|
126
|
+
writeFileSync(subtreeControlPath, '+cpu');
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Controller might already be enabled or not available
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if cgroups are available
|
|
135
|
+
*/
|
|
136
|
+
isAvailable() {
|
|
137
|
+
return this.available;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create a cgroup for an agent with CPU limits
|
|
141
|
+
*
|
|
142
|
+
* @param agentName - Unique agent identifier
|
|
143
|
+
* @param config - CPU limit configuration
|
|
144
|
+
* @returns true if cgroup was created, false if not available
|
|
145
|
+
*/
|
|
146
|
+
async createAgentCgroup(agentName, config = {}) {
|
|
147
|
+
if (!this.initialized) {
|
|
148
|
+
await this.initialize();
|
|
149
|
+
}
|
|
150
|
+
if (!this.available) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
// Validate agent name (no path traversal)
|
|
154
|
+
if (agentName.includes('/') || agentName.includes('..')) {
|
|
155
|
+
throw new Error(`Invalid agent name: ${agentName}`);
|
|
156
|
+
}
|
|
157
|
+
const cgroupPath = join(this.cgroupBase, agentName);
|
|
158
|
+
try {
|
|
159
|
+
// Create cgroup directory
|
|
160
|
+
if (!existsSync(cgroupPath)) {
|
|
161
|
+
mkdirSync(cgroupPath, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
// Configure CPU limit
|
|
164
|
+
const cpuPercent = config.cpuPercent ?? DEFAULT_CPU_PERCENT;
|
|
165
|
+
const cpuPeriodUs = config.cpuPeriodUs ?? DEFAULT_CPU_PERIOD_US;
|
|
166
|
+
// cpu.max format: "$MAX $PERIOD" in microseconds
|
|
167
|
+
// For 50% of one CPU: "50000 100000" (50ms max per 100ms period)
|
|
168
|
+
const cpuMaxUs = Math.floor((cpuPercent / 100) * cpuPeriodUs);
|
|
169
|
+
const cpuMaxValue = `${cpuMaxUs} ${cpuPeriodUs}`;
|
|
170
|
+
writeFileSync(join(cgroupPath, 'cpu.max'), cpuMaxValue);
|
|
171
|
+
// Track the cgroup
|
|
172
|
+
const info = {
|
|
173
|
+
name: agentName,
|
|
174
|
+
path: cgroupPath,
|
|
175
|
+
pids: [],
|
|
176
|
+
cpuLimit: { cpuPercent, cpuPeriodUs },
|
|
177
|
+
createdAt: Date.now(),
|
|
178
|
+
};
|
|
179
|
+
this.agentCgroups.set(agentName, info);
|
|
180
|
+
this.emit('cgroup-created', { agentName, path: cgroupPath, cpuPercent });
|
|
181
|
+
console.info(`[cgroup-manager] Created cgroup for ${agentName} with ${cpuPercent}% CPU limit`);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const error = new Error(`Failed to create cgroup for ${agentName}: ${err.message}`);
|
|
186
|
+
this.emit('error', error);
|
|
187
|
+
console.warn(`[cgroup-manager] ${error.message}`);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Add a process to an agent's cgroup
|
|
193
|
+
*
|
|
194
|
+
* @param agentName - Agent name
|
|
195
|
+
* @param pid - Process ID to add
|
|
196
|
+
* @returns true if process was added
|
|
197
|
+
*/
|
|
198
|
+
async addProcess(agentName, pid) {
|
|
199
|
+
if (!this.available) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const info = this.agentCgroups.get(agentName);
|
|
203
|
+
if (!info) {
|
|
204
|
+
console.warn(`[cgroup-manager] No cgroup found for agent ${agentName}`);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
// Write PID to cgroup.procs
|
|
209
|
+
writeFileSync(join(info.path, 'cgroup.procs'), String(pid));
|
|
210
|
+
info.pids.push(pid);
|
|
211
|
+
this.emit('process-added', { agentName, pid });
|
|
212
|
+
console.info(`[cgroup-manager] Added process ${pid} to cgroup ${agentName}`);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
console.warn(`[cgroup-manager] Failed to add process ${pid} to cgroup ${agentName}: ${err.message}`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Remove an agent's cgroup
|
|
222
|
+
*
|
|
223
|
+
* @param agentName - Agent name
|
|
224
|
+
* @returns true if cgroup was removed
|
|
225
|
+
*/
|
|
226
|
+
async removeAgentCgroup(agentName) {
|
|
227
|
+
if (!this.available) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const info = this.agentCgroups.get(agentName);
|
|
231
|
+
if (!info) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
// Move processes to parent cgroup first (required before removal)
|
|
236
|
+
const parentProcs = join(this.cgroupBase, 'cgroup.procs');
|
|
237
|
+
for (const pid of info.pids) {
|
|
238
|
+
try {
|
|
239
|
+
writeFileSync(parentProcs, String(pid));
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Process might have exited
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Remove the cgroup directory
|
|
246
|
+
rmSync(info.path, { recursive: true, force: true });
|
|
247
|
+
this.agentCgroups.delete(agentName);
|
|
248
|
+
this.emit('cgroup-removed', { agentName });
|
|
249
|
+
console.info(`[cgroup-manager] Removed cgroup for ${agentName}`);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
console.warn(`[cgroup-manager] Failed to remove cgroup ${agentName}: ${err.message}`);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Update CPU limit for an existing agent cgroup
|
|
259
|
+
*
|
|
260
|
+
* @param agentName - Agent name
|
|
261
|
+
* @param cpuPercent - New CPU percentage limit
|
|
262
|
+
*/
|
|
263
|
+
async updateCpuLimit(agentName, cpuPercent) {
|
|
264
|
+
if (!this.available) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const info = this.agentCgroups.get(agentName);
|
|
268
|
+
if (!info) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const cpuPeriodUs = info.cpuLimit.cpuPeriodUs ?? DEFAULT_CPU_PERIOD_US;
|
|
273
|
+
const cpuMaxUs = Math.floor((cpuPercent / 100) * cpuPeriodUs);
|
|
274
|
+
const cpuMaxValue = `${cpuMaxUs} ${cpuPeriodUs}`;
|
|
275
|
+
writeFileSync(join(info.path, 'cpu.max'), cpuMaxValue);
|
|
276
|
+
info.cpuLimit.cpuPercent = cpuPercent;
|
|
277
|
+
console.info(`[cgroup-manager] Updated CPU limit for ${agentName} to ${cpuPercent}%`);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
console.warn(`[cgroup-manager] Failed to update CPU limit for ${agentName}: ${err.message}`);
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get current CPU usage for an agent (if available)
|
|
287
|
+
*
|
|
288
|
+
* @param agentName - Agent name
|
|
289
|
+
* @returns CPU usage stats or null
|
|
290
|
+
*/
|
|
291
|
+
getCpuStats(agentName) {
|
|
292
|
+
if (!this.available) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const info = this.agentCgroups.get(agentName);
|
|
296
|
+
if (!info) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const statPath = join(info.path, 'cpu.stat');
|
|
301
|
+
if (!existsSync(statPath)) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const stat = readFileSync(statPath, 'utf-8');
|
|
305
|
+
const lines = stat.trim().split('\n');
|
|
306
|
+
const stats = {};
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
const [key, value] = line.split(' ');
|
|
309
|
+
stats[key] = parseInt(value, 10);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
usageUsec: stats['usage_usec'] ?? 0,
|
|
313
|
+
throttledUsec: stats['throttled_usec'] ?? 0,
|
|
314
|
+
periods: stats['nr_periods'] ?? 0,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get info about all agent cgroups
|
|
323
|
+
*/
|
|
324
|
+
getAllAgentCgroups() {
|
|
325
|
+
return Array.from(this.agentCgroups.values());
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Clean up orphaned cgroups (e.g., after crash)
|
|
329
|
+
*/
|
|
330
|
+
async cleanupOrphanedCgroups() {
|
|
331
|
+
if (!this.available || !existsSync(this.cgroupBase)) {
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
let cleaned = 0;
|
|
335
|
+
try {
|
|
336
|
+
const entries = readdirSync(this.cgroupBase, { withFileTypes: true });
|
|
337
|
+
for (const entry of entries) {
|
|
338
|
+
if (!entry.isDirectory())
|
|
339
|
+
continue;
|
|
340
|
+
// Skip if we're tracking this cgroup
|
|
341
|
+
if (this.agentCgroups.has(entry.name))
|
|
342
|
+
continue;
|
|
343
|
+
// Try to remove orphaned cgroup
|
|
344
|
+
try {
|
|
345
|
+
const cgroupPath = join(this.cgroupBase, entry.name);
|
|
346
|
+
rmSync(cgroupPath, { recursive: true, force: true });
|
|
347
|
+
cleaned++;
|
|
348
|
+
console.info(`[cgroup-manager] Cleaned up orphaned cgroup: ${entry.name}`);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Might still have processes
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Ignore errors during cleanup
|
|
357
|
+
}
|
|
358
|
+
return cleaned;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Shutdown and clean up all cgroups
|
|
362
|
+
*/
|
|
363
|
+
async shutdown() {
|
|
364
|
+
for (const [agentName] of this.agentCgroups) {
|
|
365
|
+
await this.removeAgentCgroup(agentName);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Singleton instance
|
|
370
|
+
let cgroupManagerInstance = null;
|
|
371
|
+
/**
|
|
372
|
+
* Get the singleton CgroupManager instance
|
|
373
|
+
*/
|
|
374
|
+
export function getCgroupManager(cgroupBase) {
|
|
375
|
+
if (!cgroupManagerInstance) {
|
|
376
|
+
cgroupManagerInstance = new CgroupManager(cgroupBase);
|
|
377
|
+
}
|
|
378
|
+
return cgroupManagerInstance;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Format bytes for display
|
|
382
|
+
*/
|
|
383
|
+
export function formatCpuTime(usec) {
|
|
384
|
+
if (usec < 1000) {
|
|
385
|
+
return `${usec}µs`;
|
|
386
|
+
}
|
|
387
|
+
else if (usec < 1000000) {
|
|
388
|
+
return `${(usec / 1000).toFixed(2)}ms`;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
return `${(usec / 1000000).toFixed(2)}s`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=cgroup-manager.js.map
|
|
@@ -65,4 +65,5 @@ export { CrashInsightsService, getCrashInsights, type CrashRecord, type CrashAna
|
|
|
65
65
|
export { StatelessLeadCoordinator, createStatelessLead, type BeadsTask, type LeadHeartbeat, type StatelessLeadConfig, } from './stateless-lead.js';
|
|
66
66
|
export { LeaderWatchdog, createLeaderWatchdog, type LeaderWatchdogConfig, type ElectionResult, } from './leader-watchdog.js';
|
|
67
67
|
export { GossipHealthMonitor, createGossipHealth, type GossipHeartbeat, type PeerHealth, type GossipHealthConfig, } from './gossip-health.js';
|
|
68
|
+
export { CgroupManager, getCgroupManager, formatCpuTime, type CpuLimitConfig, type AgentCgroupInfo, } from './cgroup-manager.js';
|
|
68
69
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -65,4 +65,5 @@ export { CrashInsightsService, getCrashInsights, } from './crash-insights.js';
|
|
|
65
65
|
export { StatelessLeadCoordinator, createStatelessLead, } from './stateless-lead.js';
|
|
66
66
|
export { LeaderWatchdog, createLeaderWatchdog, } from './leader-watchdog.js';
|
|
67
67
|
export { GossipHealthMonitor, createGossipHealth, } from './gossip-health.js';
|
|
68
|
+
export { CgroupManager, getCgroupManager, formatCpuTime, } from './cgroup-manager.js';
|
|
68
69
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Lightweight SDK for agent-to-agent communication via Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@agent-relay/protocol": "2.0.
|
|
58
|
+
"@agent-relay/protocol": "2.0.7"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
|
61
61
|
"node": ">=18.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/storage",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Storage adapters and interfaces for Relay message/session persistence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@agent-relay/protocol": "2.0.
|
|
59
|
+
"@agent-relay/protocol": "2.0.7"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/trajectory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Trajectory integration utilities (trail/PDERO) for Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/config": "2.0.
|
|
25
|
+
"@agent-relay/config": "2.0.7"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/user-directory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "User directory service for agent-relay (per-user credential storage)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/resiliency": "2.0.
|
|
25
|
+
"@agent-relay/resiliency": "2.0.7"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -42,6 +42,8 @@ export interface RelayPtyOrchestratorConfig extends BaseWrapperConfig {
|
|
|
42
42
|
debug?: boolean;
|
|
43
43
|
/** Force headless mode (use pipes instead of inheriting TTY) */
|
|
44
44
|
headless?: boolean;
|
|
45
|
+
/** CPU limit percentage per agent (1-100 per core, e.g., 50 = 50% of one core). Requires cgroups v2. */
|
|
46
|
+
cpuLimitPercent?: number;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Events emitted by RelayPtyOrchestrator
|
|
@@ -109,6 +111,8 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
|
|
|
109
111
|
private isGracefulStop;
|
|
110
112
|
private memoryMonitor;
|
|
111
113
|
private memoryAlertHandler;
|
|
114
|
+
private cgroupManager;
|
|
115
|
+
private hasCgroupSetup;
|
|
112
116
|
constructor(config: RelayPtyOrchestratorConfig);
|
|
113
117
|
/**
|
|
114
118
|
* Debug log - only outputs when debug is enabled
|
|
@@ -146,6 +150,10 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
|
|
|
146
150
|
* Spawn the relay-pty process
|
|
147
151
|
*/
|
|
148
152
|
private spawnRelayPty;
|
|
153
|
+
/**
|
|
154
|
+
* Set up cgroup CPU limit for this agent
|
|
155
|
+
*/
|
|
156
|
+
private setupCgroupLimit;
|
|
149
157
|
/**
|
|
150
158
|
* Handle output from relay-pty stdout (headless mode only)
|
|
151
159
|
* In interactive mode, stdout goes directly to terminal via inherited stdio
|
|
@@ -223,10 +231,6 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
|
|
|
223
231
|
* Inject a message into the agent via socket
|
|
224
232
|
*/
|
|
225
233
|
private injectMessage;
|
|
226
|
-
/** Maximum retries for failed injections before giving up */
|
|
227
|
-
private static readonly MAX_INJECTION_RETRIES;
|
|
228
|
-
/** Backoff delay multiplier (ms) for retries: delay = BASE * 2^retryCount */
|
|
229
|
-
private static readonly INJECTION_RETRY_BASE_MS;
|
|
230
234
|
/**
|
|
231
235
|
* Process queued messages
|
|
232
236
|
*/
|