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.
Files changed (145) hide show
  1. package/deploy/workspace/entrypoint.sh +80 -22
  2. package/deploy/workspace/gh-credential-relay +90 -0
  3. package/dist/dashboard/out/404.html +1 -1
  4. package/{packages/dashboard/ui-dist/_next/static/chunks/677-7323947c23b35979.js → dist/dashboard/out/_next/static/chunks/320-22ebe7be58cf982a.js} +1 -1
  5. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-9914652442f7e4fb.js +1 -0
  6. 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
  7. 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
  8. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-113060009ef35bc2.js +1 -0
  9. 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
  10. package/dist/dashboard/out/_next/static/chunks/app/layout-6b91e33784c20610.js +1 -0
  11. 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
  12. 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
  13. package/dist/dashboard/out/_next/static/chunks/app/{page-4e64923d73c35bc9.js → page-487fa38f041815c1.js} +1 -1
  14. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-9db3ebdfa567a7c9.js +1 -0
  15. package/dist/dashboard/out/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +1 -0
  16. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/{page-84161c802b020a1f.js → page-4dbe33f0f7691b7c.js} +1 -1
  17. package/dist/dashboard/out/_next/static/chunks/app/signup/{page-18a4665665f6be11.js → page-1ede2205b58649ca.js} +1 -1
  18. package/dist/dashboard/out/_next/static/chunks/main-app-fdbeb09028f57c9f.js +1 -0
  19. package/dist/dashboard/out/app/onboarding.html +1 -1
  20. package/dist/dashboard/out/app/onboarding.txt +2 -2
  21. package/dist/dashboard/out/app.html +1 -1
  22. package/dist/dashboard/out/app.txt +2 -2
  23. package/dist/dashboard/out/cloud/link.html +1 -1
  24. package/dist/dashboard/out/cloud/link.txt +2 -2
  25. package/dist/dashboard/out/connect-repos.html +1 -1
  26. package/dist/dashboard/out/connect-repos.txt +2 -2
  27. package/dist/dashboard/out/history.html +1 -1
  28. package/dist/dashboard/out/history.txt +2 -2
  29. package/dist/dashboard/out/index.html +1 -1
  30. package/dist/dashboard/out/index.txt +2 -2
  31. package/dist/dashboard/out/login.html +2 -2
  32. package/dist/dashboard/out/login.txt +2 -2
  33. package/dist/dashboard/out/metrics.html +1 -1
  34. package/dist/dashboard/out/metrics.txt +2 -2
  35. package/dist/dashboard/out/pricing.html +2 -2
  36. package/dist/dashboard/out/pricing.txt +2 -2
  37. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  38. package/dist/dashboard/out/providers/setup/claude.txt +2 -2
  39. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  40. package/dist/dashboard/out/providers/setup/codex.txt +2 -2
  41. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  42. package/dist/dashboard/out/providers/setup/cursor.txt +2 -2
  43. package/dist/dashboard/out/providers.html +1 -1
  44. package/dist/dashboard/out/providers.txt +2 -2
  45. package/dist/dashboard/out/signup.html +2 -2
  46. package/dist/dashboard/out/signup.txt +2 -2
  47. package/package.json +14 -14
  48. package/packages/api-types/package.json +1 -1
  49. package/packages/bridge/dist/spawner.js +9 -0
  50. package/packages/bridge/package.json +7 -7
  51. package/packages/cloud/dist/server.js +3 -3
  52. package/packages/cloud/package.json +6 -6
  53. package/packages/config/package.json +2 -2
  54. package/packages/continuity/package.json +1 -1
  55. package/packages/daemon/package.json +12 -12
  56. package/packages/dashboard/dist/server.js +9 -9
  57. package/packages/dashboard/package.json +12 -12
  58. package/packages/dashboard/ui/react-components/App.tsx +21 -435
  59. package/packages/dashboard/ui/react-components/ChannelChat.tsx +29 -75
  60. package/packages/dashboard/ui/react-components/MessageComposer.tsx +457 -0
  61. package/packages/dashboard/ui-dist/404.html +1 -1
  62. package/packages/dashboard/ui-dist/_next/static/chunks/320-22ebe7be58cf982a.js +1 -0
  63. 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
  64. package/packages/dashboard/ui-dist/_next/static/chunks/app/{page-4e64923d73c35bc9.js → page-487fa38f041815c1.js} +1 -1
  65. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  66. package/packages/dashboard/ui-dist/app/onboarding.txt +2 -2
  67. package/packages/dashboard/ui-dist/app.html +1 -1
  68. package/packages/dashboard/ui-dist/app.txt +2 -2
  69. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  70. package/packages/dashboard/ui-dist/cloud/link.txt +2 -2
  71. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  72. package/packages/dashboard/ui-dist/connect-repos.txt +2 -2
  73. package/packages/dashboard/ui-dist/history.html +1 -1
  74. package/packages/dashboard/ui-dist/history.txt +2 -2
  75. package/packages/dashboard/ui-dist/index.html +1 -1
  76. package/packages/dashboard/ui-dist/index.txt +2 -2
  77. package/packages/dashboard/ui-dist/login.html +2 -2
  78. package/packages/dashboard/ui-dist/login.txt +2 -2
  79. package/packages/dashboard/ui-dist/metrics.html +1 -1
  80. package/packages/dashboard/ui-dist/metrics.txt +2 -2
  81. package/packages/dashboard/ui-dist/pricing.html +2 -2
  82. package/packages/dashboard/ui-dist/pricing.txt +2 -2
  83. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  84. package/packages/dashboard/ui-dist/providers/setup/claude.txt +2 -2
  85. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  86. package/packages/dashboard/ui-dist/providers/setup/codex.txt +2 -2
  87. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  88. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +2 -2
  89. package/packages/dashboard/ui-dist/providers.html +1 -1
  90. package/packages/dashboard/ui-dist/providers.txt +2 -2
  91. package/packages/dashboard/ui-dist/signup.html +2 -2
  92. package/packages/dashboard/ui-dist/signup.txt +2 -2
  93. package/packages/dashboard-server/dist/server.js +5 -5
  94. package/packages/dashboard-server/package.json +12 -12
  95. package/packages/hooks/package.json +4 -4
  96. package/packages/mcp/package.json +2 -2
  97. package/packages/memory/package.json +2 -2
  98. package/packages/policy/package.json +2 -2
  99. package/packages/protocol/package.json +1 -1
  100. package/packages/resiliency/dist/cgroup-manager.d.ts +152 -0
  101. package/packages/resiliency/dist/cgroup-manager.js +394 -0
  102. package/packages/resiliency/dist/index.d.ts +1 -0
  103. package/packages/resiliency/dist/index.js +1 -0
  104. package/packages/resiliency/package.json +1 -1
  105. package/packages/sdk/package.json +2 -2
  106. package/packages/spawner/package.json +1 -1
  107. package/packages/state/package.json +1 -1
  108. package/packages/storage/package.json +2 -2
  109. package/packages/telemetry/package.json +1 -1
  110. package/packages/trajectory/package.json +2 -2
  111. package/packages/user-directory/package.json +2 -2
  112. package/packages/utils/package.json +1 -1
  113. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +8 -4
  114. package/packages/wrapper/dist/relay-pty-orchestrator.js +47 -25
  115. package/packages/wrapper/package.json +6 -6
  116. package/dist/dashboard/out/_next/static/chunks/320-900169c942e31422.js +0 -1
  117. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +0 -1
  118. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-5011ae044b90449d.js +0 -1
  119. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +0 -1
  120. package/dist/dashboard/out/_next/static/chunks/app/history/page-b2ce7c96ed0931da.js +0 -1
  121. package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
  122. package/dist/dashboard/out/_next/static/chunks/app/login/page-6ec54eee75877971.js +0 -1
  123. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-bf2cb1e5915bc92d.js +0 -1
  124. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +0 -1
  125. package/dist/dashboard/out/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +0 -1
  126. package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +0 -1
  127. package/packages/dashboard/ui-dist/_next/static/chunks/320-900169c942e31422.js +0 -1
  128. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +0 -1
  129. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/page-44813aa26ad19681.js +0 -1
  130. package/packages/dashboard/ui-dist/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +0 -1
  131. package/packages/dashboard/ui-dist/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
  132. package/packages/dashboard/ui-dist/_next/static/chunks/app/page-7993778218818ace.js +0 -1
  133. package/packages/dashboard/ui-dist/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +0 -1
  134. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +0 -1
  135. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/setup/[provider]/page-84161c802b020a1f.js +0 -1
  136. package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-18a4665665f6be11.js +0 -1
  137. package/packages/dashboard/ui-dist/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +0 -1
  138. /package/dist/dashboard/out/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_buildManifest.js +0 -0
  139. /package/dist/dashboard/out/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_ssgManifest.js +0 -0
  140. /package/packages/dashboard/ui-dist/_next/static/{KIxE0Ds_zdGuDJDQu7_sb → 1yt8VDAusp2yTBf4JFA7F}/_buildManifest.js +0 -0
  141. /package/packages/dashboard/ui-dist/_next/static/{KIxE0Ds_zdGuDJDQu7_sb → 1yt8VDAusp2yTBf4JFA7F}/_ssgManifest.js +0 -0
  142. /package/packages/dashboard/ui-dist/_next/static/{SoK46dEi3IsNBVWXD9x0L → chdCrViSD2yReZElqRfk0}/_buildManifest.js +0 -0
  143. /package/packages/dashboard/ui-dist/_next/static/{SoK46dEi3IsNBVWXD9x0L → chdCrViSD2yReZElqRfk0}/_ssgManifest.js +0 -0
  144. /package/packages/dashboard/ui-dist/_next/static/{lIJs7zSKBaI58kpqegulQ → loxKCRf0rbwVD8vl_Gw60}/_buildManifest.js +0 -0
  145. /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/resiliency",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Health monitoring, logging, metrics, and crash resilience utilities for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/sdk",
3
- "version": "2.0.6",
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.6"
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/spawner",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Agent spawning types and utilities for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/state",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Agent state persistence for non-hook CLIs (Codex, Gemini, etc.)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/storage",
3
- "version": "2.0.6",
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.6"
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/telemetry",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Anonymous telemetry for Agent Relay usage analytics",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/trajectory",
3
- "version": "2.0.6",
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.6"
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.6",
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.6"
25
+ "@agent-relay/resiliency": "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/utils",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Shared utilities for agent-relay: logging, name generation, command resolution, update checking",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
  */