@vibecheckai/cli 3.9.1 → 4.0.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.
- package/README.md +1 -1
- package/bin/runners/context/generators/cursor-enhanced.js +99 -13
- package/mcp-server/.eslintrc.json +24 -0
- package/mcp-server/README.md +425 -135
- package/mcp-server/SPEC.md +583 -0
- package/mcp-server/configs/README.md +172 -0
- package/mcp-server/configs/claude-desktop-pro.json +31 -0
- package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
- package/mcp-server/configs/claude-desktop.json +19 -0
- package/mcp-server/configs/cursor-mcp.json +21 -0
- package/mcp-server/configs/windsurf-mcp.json +17 -0
- package/mcp-server/mcp-config.example.json +9 -0
- package/mcp-server/package.json +49 -34
- package/mcp-server/src/cli.ts +185 -0
- package/mcp-server/src/index.ts +85 -0
- package/mcp-server/src/server.ts +1933 -0
- package/mcp-server/src/services/cache-service.ts +466 -0
- package/mcp-server/src/services/cli-service.ts +345 -0
- package/mcp-server/src/services/context-manager.ts +717 -0
- package/mcp-server/src/services/firewall-service.ts +662 -0
- package/mcp-server/src/services/git-service.ts +671 -0
- package/mcp-server/src/services/index.ts +52 -0
- package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
- package/mcp-server/src/services/session-service.ts +550 -0
- package/mcp-server/src/services/tier-service.ts +470 -0
- package/mcp-server/src/types.ts +351 -0
- package/mcp-server/tsconfig.json +16 -27
- package/package.json +6 -6
- package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
- package/mcp-server/.specs/architecture.mdc +0 -90
- package/mcp-server/.specs/security.mdc +0 -30
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-checkpoint.js +0 -364
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/architect-tools.js +0 -707
- package/mcp-server/audit-mcp.js +0 -206
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/codebase-architect-tools.js +0 -838
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/consolidated-tools.js +0 -1170
- package/mcp-server/deprecation-middleware.js +0 -282
- package/mcp-server/handlers/index.ts +0 -15
- package/mcp-server/handlers/tool-handler.ts +0 -593
- package/mcp-server/hygiene-tools.js +0 -428
- package/mcp-server/index-v1.js +0 -698
- package/mcp-server/index.js +0 -2940
- package/mcp-server/intelligence-tools.js +0 -664
- package/mcp-server/intent-drift-tools.js +0 -873
- package/mcp-server/intent-firewall-interceptor.js +0 -529
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/cache-wrapper.cjs +0 -383
- package/mcp-server/lib/error-envelope.js +0 -138
- package/mcp-server/lib/executor.ts +0 -499
- package/mcp-server/lib/index.ts +0 -29
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/lib/rate-limiter.js +0 -166
- package/mcp-server/lib/sandbox.test.ts +0 -519
- package/mcp-server/lib/sandbox.ts +0 -395
- package/mcp-server/lib/types.ts +0 -267
- package/mcp-server/logger.js +0 -173
- package/mcp-server/manifest.json +0 -473
- package/mcp-server/mdc-generator.js +0 -298
- package/mcp-server/premium-tools.js +0 -1275
- package/mcp-server/proof-tools.js +0 -571
- package/mcp-server/registry/tool-registry.js +0 -586
- package/mcp-server/registry/tools.json +0 -619
- package/mcp-server/registry.test.ts +0 -340
- package/mcp-server/test-mcp.js +0 -108
- package/mcp-server/test-tools.js +0 -36
- package/mcp-server/tests/tier-gating.test.js +0 -297
- package/mcp-server/tier-auth.js +0 -767
- package/mcp-server/tools/index.js +0 -72
- package/mcp-server/tools-reorganized.ts +0 -244
- package/mcp-server/tools-v3.js +0 -1004
- package/mcp-server/truth-context.js +0 -622
- package/mcp-server/truth-firewall-tools.js +0 -2183
- package/mcp-server/vibecheck-2.0-tools.js +0 -761
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/mcp-server/vibecheck-tools.js +0 -1075
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Service - Comprehensive agent session management
|
|
3
|
+
* Tracks agent interactions, provides context continuity, and enables audit trails
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
12
|
+
// Types
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
export interface AgentSession {
|
|
16
|
+
id: string;
|
|
17
|
+
agentId?: string;
|
|
18
|
+
agentName?: string;
|
|
19
|
+
startedAt: string;
|
|
20
|
+
lastActivityAt: string;
|
|
21
|
+
expiresAt: string;
|
|
22
|
+
status: 'active' | 'idle' | 'expired' | 'terminated';
|
|
23
|
+
|
|
24
|
+
// Context
|
|
25
|
+
workspacePath: string;
|
|
26
|
+
currentIntent?: SessionIntent;
|
|
27
|
+
|
|
28
|
+
// Metrics
|
|
29
|
+
metrics: SessionMetrics;
|
|
30
|
+
|
|
31
|
+
// History
|
|
32
|
+
toolCalls: ToolCallRecord[];
|
|
33
|
+
stateChanges: StateChange[];
|
|
34
|
+
|
|
35
|
+
// Security
|
|
36
|
+
securityContext: SecurityContext;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SessionIntent {
|
|
40
|
+
summary: string;
|
|
41
|
+
constraints: string[];
|
|
42
|
+
setAt: string;
|
|
43
|
+
hash: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SessionMetrics {
|
|
47
|
+
totalToolCalls: number;
|
|
48
|
+
successfulCalls: number;
|
|
49
|
+
failedCalls: number;
|
|
50
|
+
blockedCalls: number;
|
|
51
|
+
totalDurationMs: number;
|
|
52
|
+
averageLatencyMs: number;
|
|
53
|
+
|
|
54
|
+
// By category
|
|
55
|
+
callsByCategory: Record<string, number>;
|
|
56
|
+
|
|
57
|
+
// Firewall metrics
|
|
58
|
+
firewallViolations: number;
|
|
59
|
+
claimsVerified: number;
|
|
60
|
+
claimsPassed: number;
|
|
61
|
+
claimsFailed: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolCallRecord {
|
|
65
|
+
id: string;
|
|
66
|
+
timestamp: string;
|
|
67
|
+
toolName: string;
|
|
68
|
+
category: 'cli' | 'firewall' | 'prompt' | 'utility';
|
|
69
|
+
tier: 'free' | 'pro';
|
|
70
|
+
|
|
71
|
+
// Input/Output
|
|
72
|
+
inputSummary: string;
|
|
73
|
+
outputSummary?: string;
|
|
74
|
+
|
|
75
|
+
// Result
|
|
76
|
+
status: 'success' | 'error' | 'blocked';
|
|
77
|
+
durationMs: number;
|
|
78
|
+
errorMessage?: string;
|
|
79
|
+
blockReason?: string;
|
|
80
|
+
|
|
81
|
+
// Context
|
|
82
|
+
intentHash?: string;
|
|
83
|
+
firewallMode?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface StateChange {
|
|
87
|
+
timestamp: string;
|
|
88
|
+
type: 'intent_set' | 'intent_cleared' | 'mode_changed' | 'checkpoint_created' | 'fix_applied';
|
|
89
|
+
before: string;
|
|
90
|
+
after: string;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SecurityContext {
|
|
95
|
+
tier: 'free' | 'pro';
|
|
96
|
+
firewallMode: 'off' | 'observe' | 'enforce';
|
|
97
|
+
hasActiveIntent: boolean;
|
|
98
|
+
intentHash?: string;
|
|
99
|
+
lastVerificationAt?: string;
|
|
100
|
+
trustScore: number; // 0-100
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SessionSummary {
|
|
104
|
+
id: string;
|
|
105
|
+
duration: string;
|
|
106
|
+
status: 'active' | 'idle' | 'expired' | 'terminated';
|
|
107
|
+
metrics: {
|
|
108
|
+
totalCalls: number;
|
|
109
|
+
successRate: string;
|
|
110
|
+
avgLatency: string;
|
|
111
|
+
};
|
|
112
|
+
intent?: string;
|
|
113
|
+
recentActivity: string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
117
|
+
// Session Service Implementation
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
119
|
+
|
|
120
|
+
export class SessionService {
|
|
121
|
+
private currentSession: AgentSession | null = null;
|
|
122
|
+
private sessionStorePath: string;
|
|
123
|
+
private readonly SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
124
|
+
private readonly IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
125
|
+
private readonly MAX_TOOL_CALLS_STORED = 500;
|
|
126
|
+
|
|
127
|
+
constructor(workspacePath: string) {
|
|
128
|
+
this.sessionStorePath = path.join(os.homedir(), '.vibecheck', 'sessions');
|
|
129
|
+
this.ensureSessionStore();
|
|
130
|
+
this.currentSession = this.createSession(workspacePath);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private ensureSessionStore(): void {
|
|
134
|
+
if (!fs.existsSync(this.sessionStorePath)) {
|
|
135
|
+
fs.mkdirSync(this.sessionStorePath, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Create a new session
|
|
141
|
+
*/
|
|
142
|
+
private createSession(workspacePath: string): AgentSession {
|
|
143
|
+
const now = new Date();
|
|
144
|
+
const session: AgentSession = {
|
|
145
|
+
id: this.generateSessionId(),
|
|
146
|
+
startedAt: now.toISOString(),
|
|
147
|
+
lastActivityAt: now.toISOString(),
|
|
148
|
+
expiresAt: new Date(now.getTime() + this.SESSION_TIMEOUT_MS).toISOString(),
|
|
149
|
+
status: 'active',
|
|
150
|
+
workspacePath,
|
|
151
|
+
|
|
152
|
+
metrics: {
|
|
153
|
+
totalToolCalls: 0,
|
|
154
|
+
successfulCalls: 0,
|
|
155
|
+
failedCalls: 0,
|
|
156
|
+
blockedCalls: 0,
|
|
157
|
+
totalDurationMs: 0,
|
|
158
|
+
averageLatencyMs: 0,
|
|
159
|
+
callsByCategory: {},
|
|
160
|
+
firewallViolations: 0,
|
|
161
|
+
claimsVerified: 0,
|
|
162
|
+
claimsPassed: 0,
|
|
163
|
+
claimsFailed: 0,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
toolCalls: [],
|
|
167
|
+
stateChanges: [],
|
|
168
|
+
|
|
169
|
+
securityContext: {
|
|
170
|
+
tier: 'free',
|
|
171
|
+
firewallMode: 'off',
|
|
172
|
+
hasActiveIntent: false,
|
|
173
|
+
trustScore: 100,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
this.saveSession(session);
|
|
178
|
+
return session;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private generateSessionId(): string {
|
|
182
|
+
const timestamp = Date.now().toString(36);
|
|
183
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
184
|
+
return `vcs_${timestamp}_${random}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Record a tool call
|
|
189
|
+
*/
|
|
190
|
+
recordToolCall(record: Omit<ToolCallRecord, 'id' | 'timestamp'>): void {
|
|
191
|
+
if (!this.currentSession) return;
|
|
192
|
+
|
|
193
|
+
const fullRecord: ToolCallRecord = {
|
|
194
|
+
...record,
|
|
195
|
+
id: `tc_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Add to history (keep last N calls)
|
|
200
|
+
this.currentSession.toolCalls.push(fullRecord);
|
|
201
|
+
if (this.currentSession.toolCalls.length > this.MAX_TOOL_CALLS_STORED) {
|
|
202
|
+
this.currentSession.toolCalls = this.currentSession.toolCalls.slice(-this.MAX_TOOL_CALLS_STORED);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Update metrics
|
|
206
|
+
this.currentSession.metrics.totalToolCalls++;
|
|
207
|
+
this.currentSession.metrics.totalDurationMs += record.durationMs;
|
|
208
|
+
this.currentSession.metrics.averageLatencyMs =
|
|
209
|
+
this.currentSession.metrics.totalDurationMs / this.currentSession.metrics.totalToolCalls;
|
|
210
|
+
|
|
211
|
+
if (record.status === 'success') {
|
|
212
|
+
this.currentSession.metrics.successfulCalls++;
|
|
213
|
+
} else if (record.status === 'error') {
|
|
214
|
+
this.currentSession.metrics.failedCalls++;
|
|
215
|
+
} else if (record.status === 'blocked') {
|
|
216
|
+
this.currentSession.metrics.blockedCalls++;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Update category counts
|
|
220
|
+
this.currentSession.metrics.callsByCategory[record.category] =
|
|
221
|
+
(this.currentSession.metrics.callsByCategory[record.category] || 0) + 1;
|
|
222
|
+
|
|
223
|
+
// Update activity
|
|
224
|
+
this.currentSession.lastActivityAt = new Date().toISOString();
|
|
225
|
+
this.currentSession.status = 'active';
|
|
226
|
+
|
|
227
|
+
this.saveSession(this.currentSession);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Record a state change
|
|
232
|
+
*/
|
|
233
|
+
recordStateChange(change: Omit<StateChange, 'timestamp'>): void {
|
|
234
|
+
if (!this.currentSession) return;
|
|
235
|
+
|
|
236
|
+
this.currentSession.stateChanges.push({
|
|
237
|
+
...change,
|
|
238
|
+
timestamp: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Keep last 100 state changes
|
|
242
|
+
if (this.currentSession.stateChanges.length > 100) {
|
|
243
|
+
this.currentSession.stateChanges = this.currentSession.stateChanges.slice(-100);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.saveSession(this.currentSession);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Update security context
|
|
251
|
+
*/
|
|
252
|
+
updateSecurityContext(updates: Partial<SecurityContext>): void {
|
|
253
|
+
if (!this.currentSession) return;
|
|
254
|
+
|
|
255
|
+
this.currentSession.securityContext = {
|
|
256
|
+
...this.currentSession.securityContext,
|
|
257
|
+
...updates,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
this.saveSession(this.currentSession);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Set current intent
|
|
265
|
+
*/
|
|
266
|
+
setIntent(summary: string, constraints: string[]): void {
|
|
267
|
+
if (!this.currentSession) return;
|
|
268
|
+
|
|
269
|
+
const intent: SessionIntent = {
|
|
270
|
+
summary,
|
|
271
|
+
constraints,
|
|
272
|
+
setAt: new Date().toISOString(),
|
|
273
|
+
hash: this.hashIntent(summary, constraints),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const previousIntent = this.currentSession.currentIntent?.summary || 'none';
|
|
277
|
+
|
|
278
|
+
this.currentSession.currentIntent = intent;
|
|
279
|
+
this.currentSession.securityContext.hasActiveIntent = true;
|
|
280
|
+
this.currentSession.securityContext.intentHash = intent.hash;
|
|
281
|
+
|
|
282
|
+
this.recordStateChange({
|
|
283
|
+
type: 'intent_set',
|
|
284
|
+
before: previousIntent,
|
|
285
|
+
after: summary,
|
|
286
|
+
metadata: { constraints },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Clear current intent
|
|
292
|
+
*/
|
|
293
|
+
clearIntent(): void {
|
|
294
|
+
if (!this.currentSession) return;
|
|
295
|
+
|
|
296
|
+
const previousIntent = this.currentSession.currentIntent?.summary || 'none';
|
|
297
|
+
|
|
298
|
+
this.currentSession.currentIntent = undefined;
|
|
299
|
+
this.currentSession.securityContext.hasActiveIntent = false;
|
|
300
|
+
this.currentSession.securityContext.intentHash = undefined;
|
|
301
|
+
|
|
302
|
+
this.recordStateChange({
|
|
303
|
+
type: 'intent_cleared',
|
|
304
|
+
before: previousIntent,
|
|
305
|
+
after: 'none',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private hashIntent(summary: string, constraints: string[]): string {
|
|
310
|
+
const content = `${summary}|${constraints.sort().join('|')}`;
|
|
311
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Record firewall violation
|
|
316
|
+
*/
|
|
317
|
+
recordFirewallViolation(): void {
|
|
318
|
+
if (!this.currentSession) return;
|
|
319
|
+
|
|
320
|
+
this.currentSession.metrics.firewallViolations++;
|
|
321
|
+
this.currentSession.securityContext.trustScore = Math.max(
|
|
322
|
+
0,
|
|
323
|
+
this.currentSession.securityContext.trustScore - 10
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
this.saveSession(this.currentSession);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Record claim verification
|
|
331
|
+
*/
|
|
332
|
+
recordClaimVerification(passed: boolean): void {
|
|
333
|
+
if (!this.currentSession) return;
|
|
334
|
+
|
|
335
|
+
this.currentSession.metrics.claimsVerified++;
|
|
336
|
+
if (passed) {
|
|
337
|
+
this.currentSession.metrics.claimsPassed++;
|
|
338
|
+
// Slightly increase trust score on passed verification
|
|
339
|
+
this.currentSession.securityContext.trustScore = Math.min(
|
|
340
|
+
100,
|
|
341
|
+
this.currentSession.securityContext.trustScore + 2
|
|
342
|
+
);
|
|
343
|
+
} else {
|
|
344
|
+
this.currentSession.metrics.claimsFailed++;
|
|
345
|
+
// Decrease trust score on failed verification
|
|
346
|
+
this.currentSession.securityContext.trustScore = Math.max(
|
|
347
|
+
0,
|
|
348
|
+
this.currentSession.securityContext.trustScore - 15
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.currentSession.securityContext.lastVerificationAt = new Date().toISOString();
|
|
353
|
+
this.saveSession(this.currentSession);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get current session
|
|
358
|
+
*/
|
|
359
|
+
getSession(): AgentSession | null {
|
|
360
|
+
return this.currentSession;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get session summary
|
|
365
|
+
*/
|
|
366
|
+
getSessionSummary(): SessionSummary | null {
|
|
367
|
+
if (!this.currentSession) return null;
|
|
368
|
+
|
|
369
|
+
const session = this.currentSession;
|
|
370
|
+
const startTime = new Date(session.startedAt);
|
|
371
|
+
const now = new Date();
|
|
372
|
+
const durationMs = now.getTime() - startTime.getTime();
|
|
373
|
+
|
|
374
|
+
const hours = Math.floor(durationMs / (1000 * 60 * 60));
|
|
375
|
+
const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
376
|
+
const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
|
|
377
|
+
|
|
378
|
+
const successRate = session.metrics.totalToolCalls > 0
|
|
379
|
+
? ((session.metrics.successfulCalls / session.metrics.totalToolCalls) * 100).toFixed(1)
|
|
380
|
+
: '100';
|
|
381
|
+
|
|
382
|
+
const recentCalls = session.toolCalls.slice(-5).map(tc =>
|
|
383
|
+
`${tc.toolName} (${tc.status})`
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
id: session.id,
|
|
388
|
+
duration: `${hours}h ${minutes}m ${seconds}s`,
|
|
389
|
+
status: session.status,
|
|
390
|
+
metrics: {
|
|
391
|
+
totalCalls: session.metrics.totalToolCalls,
|
|
392
|
+
successRate: `${successRate}%`,
|
|
393
|
+
avgLatency: `${Math.round(session.metrics.averageLatencyMs)}ms`,
|
|
394
|
+
},
|
|
395
|
+
intent: session.currentIntent?.summary,
|
|
396
|
+
recentActivity: recentCalls,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get detailed session metrics
|
|
402
|
+
*/
|
|
403
|
+
getDetailedMetrics(): SessionMetrics & { trustScore: number; sessionDuration: string } | null {
|
|
404
|
+
if (!this.currentSession) return null;
|
|
405
|
+
|
|
406
|
+
const startTime = new Date(this.currentSession.startedAt);
|
|
407
|
+
const now = new Date();
|
|
408
|
+
const durationMs = now.getTime() - startTime.getTime();
|
|
409
|
+
const hours = Math.floor(durationMs / (1000 * 60 * 60));
|
|
410
|
+
const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
...this.currentSession.metrics,
|
|
414
|
+
trustScore: this.currentSession.securityContext.trustScore,
|
|
415
|
+
sessionDuration: `${hours}h ${minutes}m`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get tool call history
|
|
421
|
+
*/
|
|
422
|
+
getToolCallHistory(limit?: number): ToolCallRecord[] {
|
|
423
|
+
if (!this.currentSession) return [];
|
|
424
|
+
|
|
425
|
+
const calls = this.currentSession.toolCalls;
|
|
426
|
+
return limit ? calls.slice(-limit) : calls;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Get state change history
|
|
431
|
+
*/
|
|
432
|
+
getStateChangeHistory(limit?: number): StateChange[] {
|
|
433
|
+
if (!this.currentSession) return [];
|
|
434
|
+
|
|
435
|
+
const changes = this.currentSession.stateChanges;
|
|
436
|
+
return limit ? changes.slice(-limit) : changes;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check if session is healthy
|
|
441
|
+
*/
|
|
442
|
+
isSessionHealthy(): { healthy: boolean; issues: string[] } {
|
|
443
|
+
if (!this.currentSession) {
|
|
444
|
+
return { healthy: false, issues: ['No active session'] };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const issues: string[] = [];
|
|
448
|
+
const session = this.currentSession;
|
|
449
|
+
|
|
450
|
+
// Check expiry
|
|
451
|
+
if (new Date(session.expiresAt) <= new Date()) {
|
|
452
|
+
issues.push('Session has expired');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Check trust score
|
|
456
|
+
if (session.securityContext.trustScore < 50) {
|
|
457
|
+
issues.push(`Low trust score (${session.securityContext.trustScore}/100)`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check error rate
|
|
461
|
+
if (session.metrics.totalToolCalls > 10) {
|
|
462
|
+
const errorRate = session.metrics.failedCalls / session.metrics.totalToolCalls;
|
|
463
|
+
if (errorRate > 0.3) {
|
|
464
|
+
issues.push(`High error rate (${(errorRate * 100).toFixed(1)}%)`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check firewall violations
|
|
469
|
+
if (session.metrics.firewallViolations > 5) {
|
|
470
|
+
issues.push(`Multiple firewall violations (${session.metrics.firewallViolations})`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
healthy: issues.length === 0,
|
|
475
|
+
issues,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Terminate session
|
|
481
|
+
*/
|
|
482
|
+
terminateSession(reason?: string): void {
|
|
483
|
+
if (!this.currentSession) return;
|
|
484
|
+
|
|
485
|
+
this.currentSession.status = 'terminated';
|
|
486
|
+
this.recordStateChange({
|
|
487
|
+
type: 'mode_changed',
|
|
488
|
+
before: 'active',
|
|
489
|
+
after: 'terminated',
|
|
490
|
+
metadata: { reason },
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
this.saveSession(this.currentSession);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Save session to disk
|
|
498
|
+
*/
|
|
499
|
+
private saveSession(session: AgentSession): void {
|
|
500
|
+
try {
|
|
501
|
+
const filePath = path.join(this.sessionStorePath, `${session.id}.json`);
|
|
502
|
+
fs.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
503
|
+
} catch {
|
|
504
|
+
// Failed to save - continue anyway
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Load session from disk
|
|
510
|
+
*/
|
|
511
|
+
loadSession(sessionId: string): AgentSession | null {
|
|
512
|
+
try {
|
|
513
|
+
const filePath = path.join(this.sessionStorePath, `${sessionId}.json`);
|
|
514
|
+
if (fs.existsSync(filePath)) {
|
|
515
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
516
|
+
}
|
|
517
|
+
} catch {
|
|
518
|
+
// Failed to load
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* List recent sessions
|
|
525
|
+
*/
|
|
526
|
+
listRecentSessions(limit: number = 10): { id: string; startedAt: string; status: string }[] {
|
|
527
|
+
try {
|
|
528
|
+
const files = fs.readdirSync(this.sessionStorePath)
|
|
529
|
+
.filter(f => f.endsWith('.json'))
|
|
530
|
+
.map(f => {
|
|
531
|
+
const filePath = path.join(this.sessionStorePath, f);
|
|
532
|
+
const stats = fs.statSync(filePath);
|
|
533
|
+
return { file: f, mtime: stats.mtime };
|
|
534
|
+
})
|
|
535
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
|
536
|
+
.slice(0, limit);
|
|
537
|
+
|
|
538
|
+
return files.map(f => {
|
|
539
|
+
const session = this.loadSession(f.file.replace('.json', ''));
|
|
540
|
+
return session ? {
|
|
541
|
+
id: session.id,
|
|
542
|
+
startedAt: session.startedAt,
|
|
543
|
+
status: session.status,
|
|
544
|
+
} : null;
|
|
545
|
+
}).filter((s): s is { id: string; startedAt: string; status: string } => s !== null);
|
|
546
|
+
} catch {
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|