@trentapps/manager-protocol 1.3.0

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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +639 -0
  3. package/dist/analyzers/ArchitectureDetector.d.ts +44 -0
  4. package/dist/analyzers/ArchitectureDetector.d.ts.map +1 -0
  5. package/dist/analyzers/ArchitectureDetector.js +218 -0
  6. package/dist/analyzers/ArchitectureDetector.js.map +1 -0
  7. package/dist/analyzers/CSSAnalyzer.d.ts +284 -0
  8. package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -0
  9. package/dist/analyzers/CSSAnalyzer.js +1180 -0
  10. package/dist/analyzers/CSSAnalyzer.js.map +1 -0
  11. package/dist/analyzers/index.d.ts +5 -0
  12. package/dist/analyzers/index.d.ts.map +1 -0
  13. package/dist/analyzers/index.js +5 -0
  14. package/dist/analyzers/index.js.map +1 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +174 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/design-system/index.d.ts +6 -0
  20. package/dist/design-system/index.d.ts.map +1 -0
  21. package/dist/design-system/index.js +6 -0
  22. package/dist/design-system/index.js.map +1 -0
  23. package/dist/design-system/tokens.d.ts +106 -0
  24. package/dist/design-system/tokens.d.ts.map +1 -0
  25. package/dist/design-system/tokens.js +554 -0
  26. package/dist/design-system/tokens.js.map +1 -0
  27. package/dist/engine/AuditLogger.d.ts +506 -0
  28. package/dist/engine/AuditLogger.d.ts.map +1 -0
  29. package/dist/engine/AuditLogger.js +1491 -0
  30. package/dist/engine/AuditLogger.js.map +1 -0
  31. package/dist/engine/GitHubApprovalManager.d.ts +123 -0
  32. package/dist/engine/GitHubApprovalManager.d.ts.map +1 -0
  33. package/dist/engine/GitHubApprovalManager.js +347 -0
  34. package/dist/engine/GitHubApprovalManager.js.map +1 -0
  35. package/dist/engine/GitHubClient.d.ts +183 -0
  36. package/dist/engine/GitHubClient.d.ts.map +1 -0
  37. package/dist/engine/GitHubClient.js +411 -0
  38. package/dist/engine/GitHubClient.js.map +1 -0
  39. package/dist/engine/RateLimiter.d.ts +81 -0
  40. package/dist/engine/RateLimiter.d.ts.map +1 -0
  41. package/dist/engine/RateLimiter.js +215 -0
  42. package/dist/engine/RateLimiter.js.map +1 -0
  43. package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
  44. package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
  45. package/dist/engine/RuleDependencyAnalyzer.js +475 -0
  46. package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
  47. package/dist/engine/RulesEngine.d.ts +176 -0
  48. package/dist/engine/RulesEngine.d.ts.map +1 -0
  49. package/dist/engine/RulesEngine.js +705 -0
  50. package/dist/engine/RulesEngine.js.map +1 -0
  51. package/dist/engine/TaskManager.d.ts +174 -0
  52. package/dist/engine/TaskManager.d.ts.map +1 -0
  53. package/dist/engine/TaskManager.js +663 -0
  54. package/dist/engine/TaskManager.js.map +1 -0
  55. package/dist/engine/index.d.ts +11 -0
  56. package/dist/engine/index.d.ts.map +1 -0
  57. package/dist/engine/index.js +13 -0
  58. package/dist/engine/index.js.map +1 -0
  59. package/dist/index.d.ts +21 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +29 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/rules/architecture.d.ts +9 -0
  64. package/dist/rules/architecture.d.ts.map +1 -0
  65. package/dist/rules/architecture.js +322 -0
  66. package/dist/rules/architecture.js.map +1 -0
  67. package/dist/rules/azure.d.ts +7 -0
  68. package/dist/rules/azure.d.ts.map +1 -0
  69. package/dist/rules/azure.js +136 -0
  70. package/dist/rules/azure.js.map +1 -0
  71. package/dist/rules/compliance.d.ts +9 -0
  72. package/dist/rules/compliance.d.ts.map +1 -0
  73. package/dist/rules/compliance.js +286 -0
  74. package/dist/rules/compliance.js.map +1 -0
  75. package/dist/rules/condition-optimizer.d.ts +151 -0
  76. package/dist/rules/condition-optimizer.d.ts.map +1 -0
  77. package/dist/rules/condition-optimizer.js +479 -0
  78. package/dist/rules/condition-optimizer.js.map +1 -0
  79. package/dist/rules/css.d.ts +10 -0
  80. package/dist/rules/css.d.ts.map +1 -0
  81. package/dist/rules/css.js +1777 -0
  82. package/dist/rules/css.js.map +1 -0
  83. package/dist/rules/field-standards.d.ts +1172 -0
  84. package/dist/rules/field-standards.d.ts.map +1 -0
  85. package/dist/rules/field-standards.js +908 -0
  86. package/dist/rules/field-standards.js.map +1 -0
  87. package/dist/rules/flask.d.ts +7 -0
  88. package/dist/rules/flask.d.ts.map +1 -0
  89. package/dist/rules/flask.js +142 -0
  90. package/dist/rules/flask.js.map +1 -0
  91. package/dist/rules/index.d.ts +827 -0
  92. package/dist/rules/index.d.ts.map +1 -0
  93. package/dist/rules/index.js +556 -0
  94. package/dist/rules/index.js.map +1 -0
  95. package/dist/rules/ml-ai.d.ts +7 -0
  96. package/dist/rules/ml-ai.d.ts.map +1 -0
  97. package/dist/rules/ml-ai.js +148 -0
  98. package/dist/rules/ml-ai.js.map +1 -0
  99. package/dist/rules/operational.d.ts +9 -0
  100. package/dist/rules/operational.d.ts.map +1 -0
  101. package/dist/rules/operational.js +318 -0
  102. package/dist/rules/operational.js.map +1 -0
  103. package/dist/rules/patterns.d.ts +568 -0
  104. package/dist/rules/patterns.d.ts.map +1 -0
  105. package/dist/rules/patterns.js +1359 -0
  106. package/dist/rules/patterns.js.map +1 -0
  107. package/dist/rules/security.d.ts +9 -0
  108. package/dist/rules/security.d.ts.map +1 -0
  109. package/dist/rules/security.js +848 -0
  110. package/dist/rules/security.js.map +1 -0
  111. package/dist/rules/shared-patterns.d.ts +268 -0
  112. package/dist/rules/shared-patterns.d.ts.map +1 -0
  113. package/dist/rules/shared-patterns.js +556 -0
  114. package/dist/rules/shared-patterns.js.map +1 -0
  115. package/dist/rules/storage.d.ts +13 -0
  116. package/dist/rules/storage.d.ts.map +1 -0
  117. package/dist/rules/storage.js +672 -0
  118. package/dist/rules/storage.js.map +1 -0
  119. package/dist/rules/stripe.d.ts +7 -0
  120. package/dist/rules/stripe.d.ts.map +1 -0
  121. package/dist/rules/stripe.js +133 -0
  122. package/dist/rules/stripe.js.map +1 -0
  123. package/dist/rules/testing.d.ts +7 -0
  124. package/dist/rules/testing.d.ts.map +1 -0
  125. package/dist/rules/testing.js +135 -0
  126. package/dist/rules/testing.js.map +1 -0
  127. package/dist/rules/ux.d.ts +9 -0
  128. package/dist/rules/ux.d.ts.map +1 -0
  129. package/dist/rules/ux.js +280 -0
  130. package/dist/rules/ux.js.map +1 -0
  131. package/dist/rules/websocket.d.ts +7 -0
  132. package/dist/rules/websocket.d.ts.map +1 -0
  133. package/dist/rules/websocket.js +128 -0
  134. package/dist/rules/websocket.js.map +1 -0
  135. package/dist/server.d.ts +43 -0
  136. package/dist/server.d.ts.map +1 -0
  137. package/dist/server.js +1967 -0
  138. package/dist/server.js.map +1 -0
  139. package/dist/supervisor/AgentSupervisor.d.ts +195 -0
  140. package/dist/supervisor/AgentSupervisor.d.ts.map +1 -0
  141. package/dist/supervisor/AgentSupervisor.js +569 -0
  142. package/dist/supervisor/AgentSupervisor.js.map +1 -0
  143. package/dist/supervisor/ManagedServerRegistry.d.ts +185 -0
  144. package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -0
  145. package/dist/supervisor/ManagedServerRegistry.js +729 -0
  146. package/dist/supervisor/ManagedServerRegistry.js.map +1 -0
  147. package/dist/supervisor/ProjectTracker.d.ts +210 -0
  148. package/dist/supervisor/ProjectTracker.d.ts.map +1 -0
  149. package/dist/supervisor/ProjectTracker.js +709 -0
  150. package/dist/supervisor/ProjectTracker.js.map +1 -0
  151. package/dist/supervisor/index.d.ts +6 -0
  152. package/dist/supervisor/index.d.ts.map +1 -0
  153. package/dist/supervisor/index.js +6 -0
  154. package/dist/supervisor/index.js.map +1 -0
  155. package/dist/testing/index.d.ts +11 -0
  156. package/dist/testing/index.d.ts.map +1 -0
  157. package/dist/testing/index.js +12 -0
  158. package/dist/testing/index.js.map +1 -0
  159. package/dist/testing/rule-tester.d.ts +217 -0
  160. package/dist/testing/rule-tester.d.ts.map +1 -0
  161. package/dist/testing/rule-tester.examples.d.ts +57 -0
  162. package/dist/testing/rule-tester.examples.d.ts.map +1 -0
  163. package/dist/testing/rule-tester.examples.js +375 -0
  164. package/dist/testing/rule-tester.examples.js.map +1 -0
  165. package/dist/testing/rule-tester.js +381 -0
  166. package/dist/testing/rule-tester.js.map +1 -0
  167. package/dist/testing/rule-validator.d.ts +141 -0
  168. package/dist/testing/rule-validator.d.ts.map +1 -0
  169. package/dist/testing/rule-validator.js +640 -0
  170. package/dist/testing/rule-validator.js.map +1 -0
  171. package/dist/types/index.d.ts +1282 -0
  172. package/dist/types/index.d.ts.map +1 -0
  173. package/dist/types/index.js +386 -0
  174. package/dist/types/index.js.map +1 -0
  175. package/dist/utils/errors.d.ts +86 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +171 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/index.d.ts +7 -0
  180. package/dist/utils/index.d.ts.map +1 -0
  181. package/dist/utils/index.js +7 -0
  182. package/dist/utils/index.js.map +1 -0
  183. package/dist/utils/rate-limiting.d.ts +268 -0
  184. package/dist/utils/rate-limiting.d.ts.map +1 -0
  185. package/dist/utils/rate-limiting.js +403 -0
  186. package/dist/utils/rate-limiting.js.map +1 -0
  187. package/dist/utils/shared.d.ts +306 -0
  188. package/dist/utils/shared.d.ts.map +1 -0
  189. package/dist/utils/shared.js +464 -0
  190. package/dist/utils/shared.js.map +1 -0
  191. package/dist/utils/shell.d.ts +22 -0
  192. package/dist/utils/shell.d.ts.map +1 -0
  193. package/dist/utils/shell.js +29 -0
  194. package/dist/utils/shell.js.map +1 -0
  195. package/package.json +67 -0
@@ -0,0 +1,709 @@
1
+ /**
2
+ * Project Tracker - Tracks agent activity across projects
3
+ *
4
+ * This system monitors:
5
+ * - Which agents are working in which projects
6
+ * - Real-time agent activity logs per project
7
+ * - Project metadata (git info, path, status)
8
+ * - Historical activity and statistics
9
+ */
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import path from 'path';
13
+ import fs from 'fs';
14
+ import { hashString } from '../utils/shared.js';
15
+ const execAsync = promisify(exec);
16
+ /**
17
+ * Simple in-memory cache with TTL
18
+ */
19
+ class GitCache {
20
+ cache = new Map();
21
+ defaultTTL;
22
+ constructor(defaultTTLMs = 60000) {
23
+ this.defaultTTL = defaultTTLMs;
24
+ }
25
+ get(key) {
26
+ const entry = this.cache.get(key);
27
+ if (!entry)
28
+ return undefined;
29
+ if (Date.now() > entry.expiresAt) {
30
+ this.cache.delete(key);
31
+ return undefined;
32
+ }
33
+ return entry.value;
34
+ }
35
+ set(key, value, ttlMs) {
36
+ this.cache.set(key, {
37
+ value,
38
+ expiresAt: Date.now() + (ttlMs ?? this.defaultTTL)
39
+ });
40
+ }
41
+ delete(key) {
42
+ this.cache.delete(key);
43
+ }
44
+ clear() {
45
+ this.cache.clear();
46
+ }
47
+ /**
48
+ * Clean up expired entries
49
+ */
50
+ cleanup() {
51
+ const now = Date.now();
52
+ for (const [key, entry] of this.cache.entries()) {
53
+ if (now > entry.expiresAt) {
54
+ this.cache.delete(key);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ class ProjectTracker {
60
+ projects = new Map();
61
+ agents = new Map();
62
+ activityLog = new Map(); // projectId -> activities
63
+ agentActivityIndex = new Map(); // agentId -> activities (performance index)
64
+ agentToProject = new Map(); // agentId -> projectId
65
+ // Git data cache with 60-second TTL to prevent repeated blocking calls
66
+ gitCache = new GitCache(60000);
67
+ // Track pending git operations to prevent duplicate concurrent calls
68
+ pendingGitOps = new Map();
69
+ // Configuration
70
+ maxActivitiesPerProject = 1000;
71
+ activityRetentionMs = 24 * 60 * 60 * 1000; // 24 hours
72
+ agentIdleTimeoutMs = 5 * 60 * 1000; // 5 minutes of inactivity = idle
73
+ agentDisconnectTimeoutMs = 30 * 60 * 1000; // 30 minutes = disconnected
74
+ gitCacheTTLMs = 60000; // 60 seconds cache for git data
75
+ /**
76
+ * Register or update a project
77
+ */
78
+ async registerProject(projectPath) {
79
+ const projectId = this.getProjectId(projectPath);
80
+ let project = this.projects.get(projectId);
81
+ if (!project) {
82
+ project = await this.createProjectInfo(projectPath);
83
+ this.projects.set(projectId, project);
84
+ this.activityLog.set(projectId, []);
85
+ }
86
+ else {
87
+ // Update git info
88
+ await this.updateGitInfo(project, projectPath);
89
+ }
90
+ return project;
91
+ }
92
+ /**
93
+ * Record agent activity in a project
94
+ * NO LONGER auto-creates agent entries - use registerSession() explicitly
95
+ */
96
+ recordActivity(projectPath, agentId, sessionId, activity) {
97
+ const projectId = this.getProjectId(projectPath);
98
+ // Ensure project exists
99
+ if (!this.projects.has(projectId)) {
100
+ // Auto-register project
101
+ this.registerProject(projectPath).catch(console.error);
102
+ }
103
+ // Record activity
104
+ const fullActivity = {
105
+ agentId,
106
+ sessionId,
107
+ timestamp: new Date().toISOString(),
108
+ ...activity
109
+ };
110
+ const activities = this.activityLog.get(projectId) || [];
111
+ activities.unshift(fullActivity); // Add to beginning
112
+ // Trim old activities
113
+ if (activities.length > this.maxActivitiesPerProject) {
114
+ activities.splice(this.maxActivitiesPerProject);
115
+ }
116
+ this.activityLog.set(projectId, activities);
117
+ // Update agent activity index for efficient agent-based queries
118
+ const agentActivities = this.agentActivityIndex.get(agentId) || [];
119
+ agentActivities.unshift(fullActivity);
120
+ if (agentActivities.length > this.maxActivitiesPerProject) {
121
+ agentActivities.splice(this.maxActivitiesPerProject);
122
+ }
123
+ this.agentActivityIndex.set(agentId, agentActivities);
124
+ // Update project stats
125
+ this.updateProjectStats(projectId);
126
+ // Only update agent if it's already registered
127
+ if (this.agents.has(agentId)) {
128
+ this.updateAgentInfo(agentId, sessionId, projectPath);
129
+ this.agentToProject.set(agentId, projectId);
130
+ }
131
+ }
132
+ /**
133
+ * Explicitly register a session/agent working on a project
134
+ * Must be called by services or Claude instances to appear in agent list
135
+ */
136
+ registerSession(params) {
137
+ const projectId = this.getProjectId(params.projectPath);
138
+ // Ensure project exists
139
+ if (!this.projects.has(projectId)) {
140
+ this.registerProject(params.projectPath).catch(console.error);
141
+ }
142
+ let agent = this.agents.get(params.agentId);
143
+ if (!agent) {
144
+ agent = {
145
+ agentId: params.agentId,
146
+ sessionId: params.sessionId,
147
+ currentProject: projectId,
148
+ startTime: new Date().toISOString(),
149
+ lastActivity: new Date().toISOString(),
150
+ actionsCount: 0,
151
+ status: 'active',
152
+ ...(params.agentType && { type: params.agentType }),
153
+ ...(params.metadata && { metadata: params.metadata })
154
+ };
155
+ }
156
+ else {
157
+ agent.currentProject = projectId;
158
+ agent.lastActivity = new Date().toISOString();
159
+ agent.status = 'active';
160
+ if (params.metadata) {
161
+ agent.metadata = { ...agent.metadata, ...params.metadata };
162
+ }
163
+ }
164
+ this.agents.set(params.agentId, agent);
165
+ this.agentToProject.set(params.agentId, projectId);
166
+ return agent;
167
+ }
168
+ /**
169
+ * Explicitly unregister/complete a session
170
+ */
171
+ completeSession(agentId, outcome) {
172
+ const agent = this.agents.get(agentId);
173
+ if (agent) {
174
+ agent.status = 'disconnected';
175
+ agent.completedAt = new Date().toISOString();
176
+ if (outcome) {
177
+ agent.outcome = outcome;
178
+ }
179
+ this.agents.set(agentId, agent);
180
+ // Will be cleaned up by cleanupStaleAgents after retention period
181
+ }
182
+ }
183
+ /**
184
+ * Get all projects
185
+ */
186
+ getProjects() {
187
+ return Array.from(this.projects.values())
188
+ .sort((a, b) => {
189
+ // Sort by last activity, most recent first
190
+ if (!a.lastActivity && !b.lastActivity)
191
+ return 0;
192
+ if (!a.lastActivity)
193
+ return 1;
194
+ if (!b.lastActivity)
195
+ return -1;
196
+ return new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime();
197
+ });
198
+ }
199
+ /**
200
+ * Get project by ID or path
201
+ */
202
+ getProject(idOrPath) {
203
+ const projectId = idOrPath.startsWith('/') ? this.getProjectId(idOrPath) : idOrPath;
204
+ return this.projects.get(projectId);
205
+ }
206
+ /**
207
+ * Get project activities
208
+ */
209
+ getProjectActivities(projectId, limit = 100) {
210
+ const activities = this.activityLog.get(projectId) || [];
211
+ return activities.slice(0, limit);
212
+ }
213
+ /**
214
+ * Get agent activities efficiently (uses index, no N+1 queries)
215
+ */
216
+ getAgentActivities(agentId, limit = 100) {
217
+ const activities = this.agentActivityIndex.get(agentId) || [];
218
+ return activities.slice(0, limit);
219
+ }
220
+ /**
221
+ * Get recent activities across all projects efficiently
222
+ * Uses k-way merge for optimal performance (O(n log k) where n=limit, k=projects)
223
+ * Avoids fetching and sorting thousands of activities when only need a few
224
+ */
225
+ getAllRecentActivities(limit = 50) {
226
+ // Create iterators for each project's activity log
227
+ const projectIterators = [];
228
+ for (const [projectId, activities] of this.activityLog.entries()) {
229
+ if (activities.length > 0) {
230
+ const project = this.projects.get(projectId);
231
+ projectIterators.push({
232
+ projectId,
233
+ projectName: project?.name || projectId,
234
+ activities,
235
+ index: 0
236
+ });
237
+ }
238
+ }
239
+ // K-way merge using a simple approach
240
+ const result = [];
241
+ while (result.length < limit && projectIterators.length > 0) {
242
+ // Find the iterator with the most recent activity
243
+ let maxTimestamp = 0;
244
+ let maxIteratorIndex = -1;
245
+ for (let i = 0; i < projectIterators.length; i++) {
246
+ const iterator = projectIterators[i];
247
+ if (iterator.index < iterator.activities.length) {
248
+ const activity = iterator.activities[iterator.index];
249
+ const timestamp = new Date(activity.timestamp).getTime();
250
+ if (timestamp > maxTimestamp) {
251
+ maxTimestamp = timestamp;
252
+ maxIteratorIndex = i;
253
+ }
254
+ }
255
+ }
256
+ // If we found an activity, add it to results
257
+ if (maxIteratorIndex >= 0) {
258
+ const iterator = projectIterators[maxIteratorIndex];
259
+ const activity = iterator.activities[iterator.index];
260
+ result.push({
261
+ ...activity,
262
+ projectId: iterator.projectId,
263
+ projectName: iterator.projectName
264
+ });
265
+ // Move iterator forward
266
+ iterator.index++;
267
+ // Remove iterator if exhausted
268
+ if (iterator.index >= iterator.activities.length) {
269
+ projectIterators.splice(maxIteratorIndex, 1);
270
+ }
271
+ }
272
+ else {
273
+ // No more activities
274
+ break;
275
+ }
276
+ }
277
+ return result;
278
+ }
279
+ /**
280
+ * Clean up stale agents (remove disconnected agents older than retention period)
281
+ */
282
+ cleanupStaleAgents() {
283
+ const now = Date.now();
284
+ const disconnectedRetentionMs = 60 * 60 * 1000; // Keep disconnected for 1 hour
285
+ for (const [agentId, agent] of this.agents.entries()) {
286
+ const lastActivityTime = new Date(agent.lastActivity).getTime();
287
+ const inactiveDuration = now - lastActivityTime;
288
+ // Remove agents that have been disconnected for too long
289
+ if (agent.status === 'disconnected' && inactiveDuration > disconnectedRetentionMs) {
290
+ this.agents.delete(agentId);
291
+ this.agentToProject.delete(agentId);
292
+ continue;
293
+ }
294
+ // Mark idle agents as disconnected if inactive for 30+ minutes
295
+ if (inactiveDuration > this.agentDisconnectTimeoutMs) {
296
+ agent.status = 'disconnected';
297
+ this.agents.set(agentId, agent);
298
+ }
299
+ // Mark active agents as idle if inactive for 5+ minutes
300
+ else if (agent.status === 'active' && inactiveDuration > this.agentIdleTimeoutMs) {
301
+ agent.status = 'idle';
302
+ this.agents.set(agentId, agent);
303
+ }
304
+ }
305
+ }
306
+ /**
307
+ * Get all agents (automatically cleans up stale entries)
308
+ */
309
+ getAgents() {
310
+ this.cleanupStaleAgents();
311
+ return Array.from(this.agents.values())
312
+ .sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
313
+ }
314
+ /**
315
+ * Get agents working in a specific project
316
+ */
317
+ getProjectAgents(projectId) {
318
+ const agents = [];
319
+ for (const [agentId, currentProjectId] of this.agentToProject.entries()) {
320
+ if (currentProjectId === projectId) {
321
+ const agent = this.agents.get(agentId);
322
+ if (agent) {
323
+ agents.push(agent);
324
+ }
325
+ }
326
+ }
327
+ return agents.sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
328
+ }
329
+ /**
330
+ * Get agent by ID
331
+ */
332
+ getAgent(agentId) {
333
+ return this.agents.get(agentId);
334
+ }
335
+ /**
336
+ * Get overall metrics summary
337
+ */
338
+ getMetrics() {
339
+ const now = Date.now();
340
+ const last24h = now - (24 * 60 * 60 * 1000);
341
+ let totalTokens = 0;
342
+ let totalCost = 0;
343
+ let totalLatency = 0;
344
+ let latencyCount = 0;
345
+ let requestCount = 0;
346
+ let errorCount = 0;
347
+ let tokens24h = 0;
348
+ let cost24h = 0;
349
+ let requests24h = 0;
350
+ let errors24h = 0;
351
+ // Aggregate from all project activities
352
+ for (const activities of this.activityLog.values()) {
353
+ for (const activity of activities) {
354
+ const activityTime = new Date(activity.timestamp).getTime();
355
+ const isRecent = activityTime > last24h;
356
+ requestCount++;
357
+ if (isRecent)
358
+ requests24h++;
359
+ if (activity.tokensUsed) {
360
+ totalTokens += activity.tokensUsed;
361
+ if (isRecent)
362
+ tokens24h += activity.tokensUsed;
363
+ }
364
+ if (activity.estimatedCost) {
365
+ totalCost += activity.estimatedCost;
366
+ if (isRecent)
367
+ cost24h += activity.estimatedCost;
368
+ }
369
+ if (activity.latencyMs) {
370
+ totalLatency += activity.latencyMs;
371
+ latencyCount++;
372
+ }
373
+ if (activity.outcome === 'failure') {
374
+ errorCount++;
375
+ if (isRecent)
376
+ errors24h++;
377
+ }
378
+ }
379
+ }
380
+ return {
381
+ totalTokens,
382
+ totalCost,
383
+ avgLatency: latencyCount > 0 ? totalLatency / latencyCount : 0,
384
+ errorRate: requestCount > 0 ? errorCount / requestCount : 0,
385
+ requestCount,
386
+ last24h: {
387
+ tokens: tokens24h,
388
+ cost: cost24h,
389
+ requests: requests24h,
390
+ errors: errors24h
391
+ }
392
+ };
393
+ }
394
+ /**
395
+ * Get metrics for a specific project
396
+ */
397
+ getProjectMetrics(projectId) {
398
+ const now = Date.now();
399
+ const last24h = now - (24 * 60 * 60 * 1000);
400
+ let totalTokens = 0;
401
+ let totalCost = 0;
402
+ let totalLatency = 0;
403
+ let latencyCount = 0;
404
+ let requestCount = 0;
405
+ let errorCount = 0;
406
+ let tokens24h = 0;
407
+ let cost24h = 0;
408
+ let requests24h = 0;
409
+ let errors24h = 0;
410
+ const activities = this.activityLog.get(projectId) || [];
411
+ for (const activity of activities) {
412
+ const activityTime = new Date(activity.timestamp).getTime();
413
+ const isRecent = activityTime > last24h;
414
+ requestCount++;
415
+ if (isRecent)
416
+ requests24h++;
417
+ if (activity.tokensUsed) {
418
+ totalTokens += activity.tokensUsed;
419
+ if (isRecent)
420
+ tokens24h += activity.tokensUsed;
421
+ }
422
+ if (activity.estimatedCost) {
423
+ totalCost += activity.estimatedCost;
424
+ if (isRecent)
425
+ cost24h += activity.estimatedCost;
426
+ }
427
+ if (activity.latencyMs) {
428
+ totalLatency += activity.latencyMs;
429
+ latencyCount++;
430
+ }
431
+ if (activity.outcome === 'failure') {
432
+ errorCount++;
433
+ if (isRecent)
434
+ errors24h++;
435
+ }
436
+ }
437
+ return {
438
+ totalTokens,
439
+ totalCost,
440
+ avgLatency: latencyCount > 0 ? totalLatency / latencyCount : 0,
441
+ errorRate: requestCount > 0 ? errorCount / requestCount : 0,
442
+ requestCount,
443
+ last24h: {
444
+ tokens: tokens24h,
445
+ cost: cost24h,
446
+ requests: requests24h,
447
+ errors: errors24h
448
+ }
449
+ };
450
+ }
451
+ /**
452
+ * Mark agent as disconnected
453
+ */
454
+ markAgentDisconnected(agentId) {
455
+ const agent = this.agents.get(agentId);
456
+ if (agent) {
457
+ agent.status = 'disconnected';
458
+ this.agents.set(agentId, agent);
459
+ }
460
+ // Remove from project mapping
461
+ this.agentToProject.delete(agentId);
462
+ // Update project active agent count
463
+ for (const project of this.projects.values()) {
464
+ project.activeAgents = this.getProjectAgents(project.id).filter(a => a.status === 'active').length;
465
+ }
466
+ }
467
+ /**
468
+ * Clean up old data
469
+ */
470
+ cleanup() {
471
+ const now = Date.now();
472
+ const cutoff = now - this.activityRetentionMs;
473
+ // Clean up old activities
474
+ for (const [projectId, activities] of this.activityLog.entries()) {
475
+ const filtered = activities.filter(a => new Date(a.timestamp).getTime() > cutoff);
476
+ this.activityLog.set(projectId, filtered);
477
+ }
478
+ // Mark idle agents as disconnected
479
+ const idleThreshold = 5 * 60 * 1000; // 5 minutes
480
+ for (const agent of this.agents.values()) {
481
+ const lastActivity = new Date(agent.lastActivity).getTime();
482
+ if (now - lastActivity > idleThreshold && agent.status === 'active') {
483
+ agent.status = 'idle';
484
+ }
485
+ }
486
+ // Clean up expired git cache entries
487
+ this.gitCache.cleanup();
488
+ }
489
+ /**
490
+ * Get project ID from path
491
+ */
492
+ getProjectId(projectPath) {
493
+ // Use basename as ID, or full path hash if needed
494
+ const normalized = path.resolve(projectPath);
495
+ return path.basename(normalized) + '-' + this.hashStringId(normalized);
496
+ }
497
+ /**
498
+ * Create project info from path
499
+ */
500
+ async createProjectInfo(projectPath) {
501
+ const projectId = this.getProjectId(projectPath);
502
+ const name = path.basename(projectPath);
503
+ const info = {
504
+ id: projectId,
505
+ name,
506
+ path: projectPath,
507
+ type: 'directory',
508
+ activeAgents: 0,
509
+ totalActions: 0,
510
+ stats: {
511
+ totalEvents: 0,
512
+ successRate: 0,
513
+ avgRiskScore: 0,
514
+ topCategories: []
515
+ }
516
+ };
517
+ // Check if it's a git repo and get git info
518
+ try {
519
+ const gitDir = path.join(projectPath, '.git');
520
+ if (fs.existsSync(gitDir)) {
521
+ info.type = 'git';
522
+ await this.updateGitInfo(info, projectPath);
523
+ }
524
+ }
525
+ catch (err) {
526
+ // Not a git repo or error reading git info
527
+ }
528
+ return info;
529
+ }
530
+ /**
531
+ * Update git information for a project with caching and deduplication
532
+ * Uses in-memory cache with TTL to avoid repeated git calls
533
+ */
534
+ async updateGitInfo(project, projectPath) {
535
+ const cacheKey = `git:${projectPath}`;
536
+ // Check cache first
537
+ const cached = this.gitCache.get(cacheKey);
538
+ if (cached) {
539
+ this.applyGitInfo(project, cached);
540
+ return;
541
+ }
542
+ // Check if there's already a pending operation for this path
543
+ const pending = this.pendingGitOps.get(cacheKey);
544
+ if (pending) {
545
+ const gitInfo = await pending;
546
+ this.applyGitInfo(project, gitInfo);
547
+ return;
548
+ }
549
+ // Start new git fetch operation
550
+ const fetchPromise = this.fetchGitInfo(projectPath);
551
+ this.pendingGitOps.set(cacheKey, fetchPromise);
552
+ try {
553
+ const gitInfo = await fetchPromise;
554
+ // Cache the result
555
+ this.gitCache.set(cacheKey, gitInfo, this.gitCacheTTLMs);
556
+ // Apply to project
557
+ this.applyGitInfo(project, gitInfo);
558
+ }
559
+ finally {
560
+ // Clean up pending operation
561
+ this.pendingGitOps.delete(cacheKey);
562
+ }
563
+ }
564
+ /**
565
+ * Apply git info to a project
566
+ */
567
+ applyGitInfo(project, gitInfo) {
568
+ if (gitInfo.branch)
569
+ project.gitBranch = gitInfo.branch;
570
+ if (gitInfo.remote)
571
+ project.gitRemote = gitInfo.remote;
572
+ if (gitInfo.repo)
573
+ project.gitRepo = gitInfo.repo;
574
+ if (gitInfo.commit)
575
+ project.gitCommit = gitInfo.commit;
576
+ }
577
+ /**
578
+ * Fetch git information asynchronously with proper error handling
579
+ * Runs all git commands in parallel for better performance
580
+ */
581
+ async fetchGitInfo(projectPath) {
582
+ const timeout = 5000; // 5 second timeout for git commands
583
+ const gitInfo = {};
584
+ // Run all git commands in parallel
585
+ const [branchResult, remoteResult, commitResult] = await Promise.allSettled([
586
+ execAsync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath, timeout }),
587
+ execAsync('git remote get-url origin', { cwd: projectPath, timeout }),
588
+ execAsync('git rev-parse --short HEAD', { cwd: projectPath, timeout })
589
+ ]);
590
+ // Process branch result
591
+ if (branchResult.status === 'fulfilled' && branchResult.value.stdout?.trim()) {
592
+ gitInfo.branch = branchResult.value.stdout.trim();
593
+ }
594
+ else if (branchResult.status === 'rejected') {
595
+ console.warn(`[ProjectTracker] Failed to get git branch for ${projectPath}:`, branchResult.reason.message);
596
+ }
597
+ // Process remote result
598
+ if (remoteResult.status === 'fulfilled' && remoteResult.value.stdout?.trim()) {
599
+ gitInfo.remote = remoteResult.value.stdout.trim();
600
+ // Extract repo name from remote URL
601
+ const match = gitInfo.remote.match(/([^/]+\/[^/]+?)(\.git)?$/);
602
+ if (match) {
603
+ gitInfo.repo = match[1].replace('.git', '');
604
+ }
605
+ }
606
+ else if (remoteResult.status === 'rejected') {
607
+ // Remote might not exist (not pushed yet) - this is normal, don't log unless unexpected error
608
+ const err = remoteResult.reason;
609
+ if (err.code !== 128) { // Git error code 128 = no remote
610
+ console.warn(`[ProjectTracker] Failed to get git remote for ${projectPath}:`, err.message);
611
+ }
612
+ }
613
+ // Process commit result
614
+ if (commitResult.status === 'fulfilled' && commitResult.value.stdout?.trim()) {
615
+ gitInfo.commit = commitResult.value.stdout.trim();
616
+ }
617
+ else if (commitResult.status === 'rejected') {
618
+ console.warn(`[ProjectTracker] Failed to get git commit for ${projectPath}:`, commitResult.reason.message);
619
+ }
620
+ return gitInfo;
621
+ }
622
+ /**
623
+ * Invalidate git cache for a specific project (useful after git operations)
624
+ */
625
+ invalidateGitCache(projectPath) {
626
+ const cacheKey = `git:${projectPath}`;
627
+ this.gitCache.delete(cacheKey);
628
+ }
629
+ /**
630
+ * Clear all git caches
631
+ */
632
+ clearGitCache() {
633
+ this.gitCache.clear();
634
+ }
635
+ /**
636
+ * Update project statistics
637
+ */
638
+ updateProjectStats(projectId) {
639
+ const project = this.projects.get(projectId);
640
+ if (!project)
641
+ return;
642
+ const activities = this.activityLog.get(projectId) || [];
643
+ project.stats.totalEvents = activities.length;
644
+ project.totalActions = activities.length;
645
+ if (activities.length > 0) {
646
+ // Last activity
647
+ project.lastActivity = activities[0].timestamp;
648
+ // Success rate
649
+ const successes = activities.filter(a => a.outcome === 'success').length;
650
+ project.stats.successRate = successes / activities.length;
651
+ // Average risk score
652
+ const withRisk = activities.filter(a => a.riskScore !== undefined);
653
+ if (withRisk.length > 0) {
654
+ const totalRisk = withRisk.reduce((sum, a) => sum + (a.riskScore || 0), 0);
655
+ project.stats.avgRiskScore = totalRisk / withRisk.length;
656
+ }
657
+ // Top categories
658
+ const categoryCounts = new Map();
659
+ for (const activity of activities) {
660
+ const count = categoryCounts.get(activity.category) || 0;
661
+ categoryCounts.set(activity.category, count + 1);
662
+ }
663
+ project.stats.topCategories = Array.from(categoryCounts.entries())
664
+ .map(([category, count]) => ({ category, count }))
665
+ .sort((a, b) => b.count - a.count)
666
+ .slice(0, 5);
667
+ }
668
+ // Update active agents count
669
+ project.activeAgents = this.getProjectAgents(projectId).filter(a => a.status === 'active').length;
670
+ }
671
+ /**
672
+ * Update agent information
673
+ */
674
+ updateAgentInfo(agentId, sessionId, projectPath) {
675
+ const projectId = this.getProjectId(projectPath);
676
+ let agent = this.agents.get(agentId);
677
+ if (!agent) {
678
+ agent = {
679
+ agentId,
680
+ sessionId,
681
+ currentProject: projectId,
682
+ startTime: new Date().toISOString(),
683
+ lastActivity: new Date().toISOString(),
684
+ actionsCount: 1,
685
+ status: 'active'
686
+ };
687
+ }
688
+ else {
689
+ agent.currentProject = projectId;
690
+ agent.lastActivity = new Date().toISOString();
691
+ agent.actionsCount++;
692
+ agent.status = 'active';
693
+ }
694
+ this.agents.set(agentId, agent);
695
+ }
696
+ /**
697
+ * Simple string hash for generating IDs
698
+ * Uses shared hashString utility with base36 encoding
699
+ */
700
+ hashStringId(str) {
701
+ return hashString(str, { base: 36, length: 8 });
702
+ }
703
+ }
704
+ export const projectTracker = new ProjectTracker();
705
+ // Cleanup old data every hour
706
+ setInterval(() => {
707
+ projectTracker.cleanup();
708
+ }, 60 * 60 * 1000).unref();
709
+ //# sourceMappingURL=ProjectTracker.js.map