@brutalist/mcp 0.6.0 → 0.6.1
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 +3 -1
- package/dist/brutalist-server.d.ts +5 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +244 -80
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +7 -3
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +307 -48
- package/dist/cli-agents.js.map +1 -1
- package/dist/streaming/circuit-breaker.d.ts +186 -0
- package/dist/streaming/circuit-breaker.d.ts.map +1 -0
- package/dist/streaming/circuit-breaker.js +463 -0
- package/dist/streaming/circuit-breaker.js.map +1 -0
- package/dist/streaming/intelligent-buffer.d.ts +141 -0
- package/dist/streaming/intelligent-buffer.d.ts.map +1 -0
- package/dist/streaming/intelligent-buffer.js +555 -0
- package/dist/streaming/intelligent-buffer.js.map +1 -0
- package/dist/streaming/output-parser.d.ts +89 -0
- package/dist/streaming/output-parser.d.ts.map +1 -0
- package/dist/streaming/output-parser.js +349 -0
- package/dist/streaming/output-parser.js.map +1 -0
- package/dist/streaming/progress-tracker.d.ts +149 -0
- package/dist/streaming/progress-tracker.d.ts.map +1 -0
- package/dist/streaming/progress-tracker.js +519 -0
- package/dist/streaming/progress-tracker.js.map +1 -0
- package/dist/streaming/session-manager.d.ts +238 -0
- package/dist/streaming/session-manager.d.ts.map +1 -0
- package/dist/streaming/session-manager.js +546 -0
- package/dist/streaming/session-manager.js.map +1 -0
- package/dist/streaming/sse-transport.d.ts +95 -0
- package/dist/streaming/sse-transport.d.ts.map +1 -0
- package/dist/streaming/sse-transport.js +319 -0
- package/dist/streaming/sse-transport.js.map +1 -0
- package/dist/streaming/streaming-orchestrator.d.ts +153 -0
- package/dist/streaming/streaming-orchestrator.d.ts.map +1 -0
- package/dist/streaming/streaming-orchestrator.js +436 -0
- package/dist/streaming/streaming-orchestrator.js.map +1 -0
- package/dist/test-utils/process-manager.d.ts +61 -0
- package/dist/test-utils/process-manager.d.ts.map +1 -0
- package/dist/test-utils/process-manager.js +262 -0
- package/dist/test-utils/process-manager.js.map +1 -0
- package/dist/test-utils/server-harness.d.ts +73 -0
- package/dist/test-utils/server-harness.d.ts.map +1 -0
- package/dist/test-utils/server-harness.js +296 -0
- package/dist/test-utils/server-harness.js.map +1 -0
- package/dist/test-utils/streaming-fuzz.d.ts +57 -0
- package/dist/test-utils/streaming-fuzz.d.ts.map +1 -0
- package/dist/test-utils/streaming-fuzz.js +287 -0
- package/dist/test-utils/streaming-fuzz.js.map +1 -0
- package/dist/test-utils/test-isolation.d.ts +70 -0
- package/dist/test-utils/test-isolation.d.ts.map +1 -0
- package/dist/test-utils/test-isolation.js +193 -0
- package/dist/test-utils/test-isolation.js.map +1 -0
- package/dist/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +12 -6
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types/brutalist.d.ts +3 -3
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +0 -1
- package/dist/types/tool-config.d.ts.map +1 -1
- package/dist/types/tool-config.js +0 -1
- package/dist/types/tool-config.js.map +1 -1
- package/dist/utils/response-cache.d.ts +14 -7
- package/dist/utils/response-cache.d.ts.map +1 -1
- package/dist/utils/response-cache.js +173 -62
- package/dist/utils/response-cache.js.map +1 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Three brutal CLI agents that can analyze anything. Each agent brings different p
|
|
|
14
14
|
|
|
15
15
|
Real file-system analysis. Actual brutal prompts. Intelligent pagination for enterprise codebases. No participation trophies.
|
|
16
16
|
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
17
19
|
### Prerequisites
|
|
18
20
|
|
|
19
21
|
Install at least one CLI agent:
|
|
@@ -21,7 +23,7 @@ Install at least one CLI agent:
|
|
|
21
23
|
- **Codex**: Install from [OpenAI Codex](https://github.com/openai/codex-cli)
|
|
22
24
|
- **Gemini**: `npm install -g @google/gemini-cli` or authenticate via `gemini auth`
|
|
23
25
|
|
|
24
|
-
###
|
|
26
|
+
### Installation
|
|
25
27
|
|
|
26
28
|
<details>
|
|
27
29
|
<summary><strong>Claude Code</strong> — One-liner</summary>
|
|
@@ -6,12 +6,17 @@ export declare class BrutalistServer {
|
|
|
6
6
|
private cliOrchestrator;
|
|
7
7
|
private httpTransport?;
|
|
8
8
|
private responseCache;
|
|
9
|
+
private actualPort?;
|
|
10
|
+
private shutdownHandler?;
|
|
11
|
+
private activeSessions;
|
|
9
12
|
constructor(config?: BrutalistServerConfig);
|
|
10
13
|
private handleStreamingEvent;
|
|
11
14
|
private handleProgressUpdate;
|
|
12
15
|
start(): Promise<void>;
|
|
13
16
|
private startStdioServer;
|
|
14
17
|
private startHttpServer;
|
|
18
|
+
getActualPort(): number | undefined;
|
|
19
|
+
cleanup(): void;
|
|
15
20
|
private registerTools;
|
|
16
21
|
private registerSpecialTools;
|
|
17
22
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brutalist-server.d.ts","sourceRoot":"","sources":["../src/brutalist-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,OAAO,EACL,qBAAqB,EAKtB,MAAM,sBAAsB,CAAC;AAc9B,qBAAa,eAAe;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;IACrC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,aAAa,CAAgB;
|
|
1
|
+
{"version":3,"file":"brutalist-server.d.ts","sourceRoot":"","sources":["../src/brutalist-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,OAAO,EACL,qBAAqB,EAKtB,MAAM,sBAAsB,CAAC;AAc9B,qBAAa,eAAe;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;IACrC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAa;IAErC,OAAO,CAAC,cAAc,CAIjB;gBAEO,MAAM,GAAE,qBAA0B;IAsC9C,OAAO,CAAC,oBAAoB,CAqD1B;IAEF,OAAO,CAAC,oBAAoB,CAkC1B;IAEI,KAAK;YAeG,gBAAgB;YAMhB,eAAe;IAmItB,aAAa,IAAI,MAAM,GAAG,SAAS;IAKnC,OAAO,IAAI,IAAI;IAOtB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,oBAAoB;IA2F5B;;OAEG;YACW,eAAe;YAyJf,gBAAgB;IAkK9B,OAAO,CAAC,gBAAgB;YA0FV,wBAAwB;IA0FtC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,kBAAkB;IAwD1B,OAAO,CAAC,uBAAuB;IA2G/B,OAAO,CAAC,mBAAmB;YA8Bb,mBAAmB;CAUlC"}
|
package/dist/brutalist-server.js
CHANGED
|
@@ -18,11 +18,14 @@ export class BrutalistServer {
|
|
|
18
18
|
cliOrchestrator;
|
|
19
19
|
httpTransport;
|
|
20
20
|
responseCache;
|
|
21
|
+
actualPort;
|
|
22
|
+
shutdownHandler;
|
|
23
|
+
// Session tracking for security
|
|
24
|
+
activeSessions = new Map();
|
|
21
25
|
constructor(config = {}) {
|
|
22
26
|
this.config = {
|
|
23
27
|
workingDirectory: process.cwd(),
|
|
24
28
|
defaultTimeout: 1500000, // 25 minutes for thorough CLI analysis
|
|
25
|
-
enableSandbox: true,
|
|
26
29
|
transport: 'stdio', // Default to stdio for backward compatibility
|
|
27
30
|
httpPort: 3000,
|
|
28
31
|
...config
|
|
@@ -43,44 +46,91 @@ export class BrutalistServer {
|
|
|
43
46
|
name: "brutalist-mcp",
|
|
44
47
|
version: PACKAGE_VERSION,
|
|
45
48
|
capabilities: {
|
|
46
|
-
tools: {}
|
|
49
|
+
tools: {},
|
|
50
|
+
logging: {},
|
|
51
|
+
experimental: {
|
|
52
|
+
streaming: true
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
});
|
|
49
56
|
this.registerTools();
|
|
50
57
|
}
|
|
51
58
|
handleStreamingEvent = (event) => {
|
|
52
|
-
// Send streaming event via MCP server (works for both stdio and HTTP transports)
|
|
53
59
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
if (!event.sessionId) {
|
|
61
|
+
logger.warn("⚠️ Streaming event without session ID - dropping for security");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
logger.debug(`🔄 Session-scoped streaming: ${event.type} from ${event.agent} to session ${event.sessionId.substring(0, 8)}...`);
|
|
65
|
+
// For HTTP transport: send session-specific notification
|
|
66
|
+
if (this.httpTransport) {
|
|
67
|
+
// Use MCP server's notification system with session context
|
|
68
|
+
this.server.server.notification({
|
|
69
|
+
method: "notifications/message",
|
|
70
|
+
params: {
|
|
71
|
+
level: 'info',
|
|
72
|
+
data: {
|
|
73
|
+
type: 'streaming_event',
|
|
74
|
+
sessionId: event.sessionId,
|
|
75
|
+
agent: event.agent,
|
|
76
|
+
eventType: event.type,
|
|
77
|
+
content: event.content?.substring(0, 1000), // Truncate for safety
|
|
78
|
+
timestamp: event.timestamp
|
|
79
|
+
},
|
|
80
|
+
logger: 'brutalist-mcp-streaming'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// For STDIO transport: still send but with session info
|
|
85
|
+
else {
|
|
86
|
+
this.server.sendLoggingMessage({
|
|
87
|
+
level: 'info',
|
|
88
|
+
data: {
|
|
89
|
+
sessionId: event.sessionId,
|
|
90
|
+
agent: event.agent,
|
|
91
|
+
type: event.type,
|
|
92
|
+
content: event.content?.substring(0, 500) // More restrictive for stdio
|
|
93
|
+
},
|
|
94
|
+
logger: 'brutalist-mcp-streaming'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Update session activity
|
|
98
|
+
if (this.activeSessions.has(event.sessionId)) {
|
|
99
|
+
this.activeSessions.get(event.sessionId).lastActivity = Date.now();
|
|
100
|
+
}
|
|
62
101
|
}
|
|
63
102
|
catch (error) {
|
|
64
|
-
logger.error("Failed to send streaming event",
|
|
103
|
+
logger.error("💥 Failed to send session-scoped streaming event", {
|
|
104
|
+
error: error instanceof Error ? error.message : String(error),
|
|
105
|
+
sessionId: event.sessionId?.substring(0, 8)
|
|
106
|
+
});
|
|
65
107
|
}
|
|
66
108
|
};
|
|
67
|
-
handleProgressUpdate = (progressToken, progress, total, message) => {
|
|
109
|
+
handleProgressUpdate = (progressToken, progress, total, message, sessionId) => {
|
|
68
110
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
if (!sessionId) {
|
|
112
|
+
logger.warn("⚠️ Progress update without session ID - dropping for security");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
logger.debug(`📊 Session progress: ${progress}/${total} for session ${sessionId.substring(0, 8)}...`);
|
|
116
|
+
// Send progress notification with session context
|
|
71
117
|
this.server.server.notification({
|
|
72
118
|
method: "notifications/progress",
|
|
73
119
|
params: {
|
|
74
120
|
progressToken,
|
|
75
121
|
progress,
|
|
76
122
|
total,
|
|
77
|
-
message
|
|
123
|
+
message: `[${sessionId.substring(0, 8)}] ${message}`, // Include session prefix
|
|
124
|
+
sessionId // Include in notification data
|
|
78
125
|
}
|
|
79
126
|
});
|
|
80
|
-
logger.debug(`✅ Sent progress notification: ${progress}/${total}`);
|
|
127
|
+
logger.debug(`✅ Sent session-scoped progress notification: ${progress}/${total}`);
|
|
81
128
|
}
|
|
82
129
|
catch (error) {
|
|
83
|
-
logger.error("Failed to send progress notification",
|
|
130
|
+
logger.error("💥 Failed to send progress notification", {
|
|
131
|
+
error: error instanceof Error ? error.message : String(error),
|
|
132
|
+
sessionId: sessionId?.substring(0, 8)
|
|
133
|
+
});
|
|
84
134
|
}
|
|
85
135
|
};
|
|
86
136
|
async start() {
|
|
@@ -118,13 +168,57 @@ export class BrutalistServer {
|
|
|
118
168
|
// Create Express app for HTTP handling
|
|
119
169
|
const app = express();
|
|
120
170
|
app.use(express.json({ limit: '10mb' })); // Add JSON size limit for security
|
|
121
|
-
//
|
|
171
|
+
// Secure CORS implementation
|
|
122
172
|
app.use((req, res, next) => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
173
|
+
const origin = req.headers.origin;
|
|
174
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
175
|
+
// Define safe default origins for development
|
|
176
|
+
const defaultDevOrigins = [
|
|
177
|
+
'http://localhost:3000',
|
|
178
|
+
'http://127.0.0.1:3000',
|
|
179
|
+
'http://localhost:8080',
|
|
180
|
+
'http://127.0.0.1:8080',
|
|
181
|
+
'http://localhost:3001',
|
|
182
|
+
'http://127.0.0.1:3001'
|
|
183
|
+
];
|
|
184
|
+
// Get allowed origins from config or use defaults
|
|
185
|
+
const allowedOrigins = this.config.corsOrigins || defaultDevOrigins;
|
|
186
|
+
const allowWildcard = this.config.allowCORSWildcard === true && !isProduction;
|
|
187
|
+
// Determine if origin is allowed
|
|
188
|
+
let allowedOrigin = null;
|
|
189
|
+
if (allowWildcard) {
|
|
190
|
+
// Only in development with explicit opt-in
|
|
191
|
+
allowedOrigin = '*';
|
|
192
|
+
logger.warn("⚠️ Using wildcard CORS - only safe in development!");
|
|
193
|
+
}
|
|
194
|
+
else if (!origin) {
|
|
195
|
+
// No origin header (same-origin or direct server access)
|
|
196
|
+
allowedOrigin = defaultDevOrigins[0]; // Default fallback
|
|
197
|
+
}
|
|
198
|
+
else if (allowedOrigins.includes(origin)) {
|
|
199
|
+
// Explicitly allowed origin
|
|
200
|
+
allowedOrigin = origin;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Rejected origin
|
|
204
|
+
logger.warn(`🚫 CORS rejected origin: ${origin}`);
|
|
205
|
+
allowedOrigin = null;
|
|
206
|
+
}
|
|
207
|
+
// Set headers only if origin is allowed
|
|
208
|
+
if (allowedOrigin) {
|
|
209
|
+
res.header('Access-Control-Allow-Origin', allowedOrigin);
|
|
210
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
211
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
212
|
+
// Removed Authorization header for security
|
|
213
|
+
res.header('Access-Control-Allow-Credentials', 'false'); // Explicit false
|
|
214
|
+
}
|
|
126
215
|
if (req.method === 'OPTIONS') {
|
|
127
|
-
|
|
216
|
+
if (allowedOrigin) {
|
|
217
|
+
res.sendStatus(200);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
res.sendStatus(403); // Forbidden for disallowed origins
|
|
221
|
+
}
|
|
128
222
|
return;
|
|
129
223
|
}
|
|
130
224
|
next();
|
|
@@ -146,21 +240,44 @@ export class BrutalistServer {
|
|
|
146
240
|
res.json({ status: 'ok', transport: 'http-streaming', version: PACKAGE_VERSION });
|
|
147
241
|
});
|
|
148
242
|
// Start the HTTP server - bind to localhost only for security
|
|
149
|
-
const port = this.config.httpPort
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
243
|
+
const port = this.config.httpPort ?? 3000;
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const server = app.listen(port, '127.0.0.1', () => {
|
|
246
|
+
const actualPort = server.address()?.port || port;
|
|
247
|
+
this.actualPort = actualPort;
|
|
248
|
+
logger.info(`HTTP server listening on port ${actualPort}`);
|
|
249
|
+
logger.info(`MCP endpoint: http://localhost:${actualPort}/mcp`);
|
|
250
|
+
logger.info(`Health check: http://localhost:${actualPort}/health`);
|
|
251
|
+
resolve();
|
|
252
|
+
});
|
|
253
|
+
server.on('error', (error) => {
|
|
254
|
+
logger.error('HTTP server failed to start', error);
|
|
255
|
+
reject(error);
|
|
161
256
|
});
|
|
257
|
+
// Handle graceful shutdown - avoid duplicate listeners
|
|
258
|
+
if (!this.shutdownHandler) {
|
|
259
|
+
this.shutdownHandler = () => {
|
|
260
|
+
logger.info('Received SIGTERM, shutting down gracefully');
|
|
261
|
+
server.close(() => {
|
|
262
|
+
logger.info('HTTP server closed');
|
|
263
|
+
process.exit(0);
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
process.on('SIGTERM', this.shutdownHandler);
|
|
267
|
+
}
|
|
162
268
|
});
|
|
163
269
|
}
|
|
270
|
+
// Getter for actual listening port (useful for tests)
|
|
271
|
+
getActualPort() {
|
|
272
|
+
return this.actualPort;
|
|
273
|
+
}
|
|
274
|
+
// Cleanup method for tests - remove event listeners
|
|
275
|
+
cleanup() {
|
|
276
|
+
if (this.shutdownHandler) {
|
|
277
|
+
process.removeListener('SIGTERM', this.shutdownHandler);
|
|
278
|
+
this.shutdownHandler = undefined;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
164
281
|
registerTools() {
|
|
165
282
|
// Register all roast tools using unified handler - DRY principle
|
|
166
283
|
TOOL_CONFIGS.forEach(config => {
|
|
@@ -180,7 +297,6 @@ export class BrutalistServer {
|
|
|
180
297
|
debateRounds: z.number().optional().describe("Number of debate rounds (default: 2, max: 10)"),
|
|
181
298
|
context: z.string().optional().describe("Additional context for the debate"),
|
|
182
299
|
workingDirectory: z.string().optional().describe("Working directory for analysis"),
|
|
183
|
-
enableSandbox: z.boolean().optional().describe("Enable sandbox mode for security"),
|
|
184
300
|
models: z.object({
|
|
185
301
|
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
186
302
|
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
@@ -189,7 +305,7 @@ export class BrutalistServer {
|
|
|
189
305
|
}, async (args) => {
|
|
190
306
|
return this.handleToolExecution(async () => {
|
|
191
307
|
const debateRounds = Math.min(args.debateRounds || 2, 10); // Limit to max 10 rounds to prevent DoS
|
|
192
|
-
const responses = await this.executeCLIDebate(args.targetPath, debateRounds, args.context, args.workingDirectory, args.
|
|
308
|
+
const responses = await this.executeCLIDebate(args.targetPath, debateRounds, args.context, args.workingDirectory, args.models);
|
|
193
309
|
return responses;
|
|
194
310
|
});
|
|
195
311
|
});
|
|
@@ -216,7 +332,7 @@ export class BrutalistServer {
|
|
|
216
332
|
roster += "- `cli_agent_roster` - This tool (show capabilities)\n\n";
|
|
217
333
|
roster += "## CLI Agent Capabilities\n";
|
|
218
334
|
roster += "**Claude Code** - Advanced analysis with direct system prompt injection\n";
|
|
219
|
-
roster += "**Codex** -
|
|
335
|
+
roster += "**Codex** - Secure execution with embedded brutal prompts\n";
|
|
220
336
|
roster += "**Gemini CLI** - Workspace context with environment variable system prompts\n\n";
|
|
221
337
|
// Add CLI context information
|
|
222
338
|
const cliContext = await this.cliOrchestrator.detectCLIContext();
|
|
@@ -247,6 +363,38 @@ export class BrutalistServer {
|
|
|
247
363
|
async handleRoastTool(config, args, extra) {
|
|
248
364
|
try {
|
|
249
365
|
const progressToken = extra._meta?.progressToken;
|
|
366
|
+
// Extract session context for security
|
|
367
|
+
const sessionId = extra?.sessionId ||
|
|
368
|
+
extra?._meta?.sessionId ||
|
|
369
|
+
extra?.headers?.['mcp-session-id'] ||
|
|
370
|
+
`anonymous-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
371
|
+
const requestId = `${sessionId}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
372
|
+
logger.debug(`🔐 Processing request with session: ${sessionId.substring(0, 8)}..., request: ${requestId.substring(0, 12)}...`);
|
|
373
|
+
// Track session activity
|
|
374
|
+
if (!this.activeSessions.has(sessionId)) {
|
|
375
|
+
this.activeSessions.set(sessionId, {
|
|
376
|
+
startTime: Date.now(),
|
|
377
|
+
requestCount: 0,
|
|
378
|
+
lastActivity: Date.now()
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
const sessionInfo = this.activeSessions.get(sessionId);
|
|
382
|
+
sessionInfo.requestCount++;
|
|
383
|
+
sessionInfo.lastActivity = Date.now();
|
|
384
|
+
// Debug logging: Log the received arguments to file
|
|
385
|
+
const fs = require('fs');
|
|
386
|
+
const debugLog = `/tmp/brutalist-tool-debug-${Date.now()}.log`;
|
|
387
|
+
const logMessage = (msg) => {
|
|
388
|
+
try {
|
|
389
|
+
fs.appendFileSync(debugLog, `${new Date().toISOString()}: ${msg}\n`);
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
// Ignore filesystem errors
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
logMessage(`🔧 ROAST TOOL DEBUG: Tool=${config.name}, primaryArgField=${config.primaryArgField}`);
|
|
396
|
+
logMessage(`🔧 ROAST TOOL DEBUG: args=${JSON.stringify(args, null, 2)}`);
|
|
397
|
+
logMessage(`🔧 ROAST TOOL DEBUG: extra=${JSON.stringify(extra, null, 2)}`);
|
|
250
398
|
// Extract pagination parameters
|
|
251
399
|
const paginationParams = extractPaginationParams(args);
|
|
252
400
|
if (args.cursor) {
|
|
@@ -255,9 +403,9 @@ export class BrutalistServer {
|
|
|
255
403
|
}
|
|
256
404
|
// Check cache if analysis_id provided
|
|
257
405
|
if (args.analysis_id && !args.force_refresh) {
|
|
258
|
-
const cachedContent = await this.responseCache.get(args.analysis_id);
|
|
406
|
+
const cachedContent = await this.responseCache.get(args.analysis_id, sessionId);
|
|
259
407
|
if (cachedContent) {
|
|
260
|
-
logger.info(`🎯
|
|
408
|
+
logger.info(`🎯 Session-validated cache hit for analysis_id: ${args.analysis_id}`);
|
|
261
409
|
const cachedResult = {
|
|
262
410
|
success: true,
|
|
263
411
|
responses: [{
|
|
@@ -269,6 +417,9 @@ export class BrutalistServer {
|
|
|
269
417
|
};
|
|
270
418
|
return this.formatToolResponse(cachedResult, args.verbose, paginationParams, args.analysis_id);
|
|
271
419
|
}
|
|
420
|
+
else {
|
|
421
|
+
logger.info(`🔍 No valid cache entry for analysis_id: ${args.analysis_id} and session: ${sessionId?.substring(0, 8)}`);
|
|
422
|
+
}
|
|
272
423
|
}
|
|
273
424
|
// Generate cache key for this request
|
|
274
425
|
const cacheKey = this.responseCache.generateCacheKey(config.cacheKeyFields.reduce((acc, field) => {
|
|
@@ -279,7 +430,7 @@ export class BrutalistServer {
|
|
|
279
430
|
}, {}));
|
|
280
431
|
// Check if we have a cached result (unless forcing refresh)
|
|
281
432
|
if (!args.force_refresh) {
|
|
282
|
-
const cachedContent = await this.responseCache.get(cacheKey);
|
|
433
|
+
const cachedContent = await this.responseCache.get(cacheKey, sessionId);
|
|
283
434
|
if (cachedContent) {
|
|
284
435
|
const analysisId = this.responseCache.generateAnalysisId(cacheKey);
|
|
285
436
|
logger.info(`🎯 Cache hit for new request, using analysis_id: ${analysisId}`);
|
|
@@ -299,8 +450,10 @@ export class BrutalistServer {
|
|
|
299
450
|
const context = config.contextBuilder ? config.contextBuilder(args) : args.context;
|
|
300
451
|
// Get the primary argument (targetPath, idea, architecture, etc.)
|
|
301
452
|
const primaryArg = args[config.primaryArgField];
|
|
453
|
+
logMessage(`🔧 PRIMARY ARG DEBUG: primaryArgField=${config.primaryArgField}, primaryArg="${primaryArg}"`);
|
|
454
|
+
logMessage(`🔧 PRIMARY ARG DEBUG: config.analysisType="${config.analysisType}"`);
|
|
302
455
|
// Run the analysis
|
|
303
|
-
const result = await this.executeBrutalistAnalysis(config.analysisType, primaryArg, config.systemPrompt, context, args.workingDirectory, args.
|
|
456
|
+
const result = await this.executeBrutalistAnalysis(config.analysisType, primaryArg, config.systemPrompt, context, args.workingDirectory, args.preferredCLI, args.verbose, args.models, progressToken, sessionId, requestId);
|
|
304
457
|
// Cache the result if successful
|
|
305
458
|
let analysisId;
|
|
306
459
|
if (result.success && result.responses.length > 0) {
|
|
@@ -312,9 +465,11 @@ export class BrutalistServer {
|
|
|
312
465
|
acc[field] = args[field];
|
|
313
466
|
return acc;
|
|
314
467
|
}, {});
|
|
315
|
-
const { analysisId: newId } = await this.responseCache.set(cacheData, fullContent, cacheKey
|
|
468
|
+
const { analysisId: newId } = await this.responseCache.set(cacheData, fullContent, cacheKey, sessionId, // NEW: Bind to session
|
|
469
|
+
requestId // NEW: Track request
|
|
470
|
+
);
|
|
316
471
|
analysisId = newId;
|
|
317
|
-
logger.info(`✅ Cached analysis result with ID: ${analysisId}`);
|
|
472
|
+
logger.info(`✅ Cached analysis result with ID: ${analysisId} for session: ${sessionId?.substring(0, 8)}`);
|
|
318
473
|
}
|
|
319
474
|
}
|
|
320
475
|
return this.formatToolResponse(result, args.verbose, paginationParams, analysisId);
|
|
@@ -323,12 +478,11 @@ export class BrutalistServer {
|
|
|
323
478
|
return this.formatErrorResponse(error);
|
|
324
479
|
}
|
|
325
480
|
}
|
|
326
|
-
async executeCLIDebate(targetPath, debateRounds, context, workingDirectory,
|
|
481
|
+
async executeCLIDebate(targetPath, debateRounds, context, workingDirectory, models) {
|
|
327
482
|
logger.debug("Executing CLI debate", {
|
|
328
483
|
targetPath,
|
|
329
484
|
debateRounds,
|
|
330
485
|
workingDirectory,
|
|
331
|
-
enableSandbox
|
|
332
486
|
});
|
|
333
487
|
try {
|
|
334
488
|
// Get CLI context
|
|
@@ -372,13 +526,14 @@ Remember: You are ${agent.toUpperCase()}, the passionate champion of ${position.
|
|
|
372
526
|
logger.info(`🎭 ${agent.toUpperCase()} preparing initial position: ${position.split(':')[0]}`);
|
|
373
527
|
const response = await this.cliOrchestrator.executeSingleCLI(agent, assignedPrompt, assignedPrompt, {
|
|
374
528
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
375
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
376
529
|
timeout: (this.config.defaultTimeout || 60000) * 2,
|
|
377
530
|
models: models ? { [agent]: models[agent] } : undefined
|
|
378
531
|
});
|
|
379
532
|
if (response.success) {
|
|
380
533
|
debateContext.push(response);
|
|
381
|
-
|
|
534
|
+
if (response.output) {
|
|
535
|
+
fullDebateTranscript.get(agent)?.push(response.output);
|
|
536
|
+
}
|
|
382
537
|
}
|
|
383
538
|
}
|
|
384
539
|
// Subsequent rounds: Turn-based responses attacking specific arguments
|
|
@@ -420,13 +575,14 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
420
575
|
logger.info(`🔥 Round ${round}: ${currentAgent.toUpperCase()} responding to opponents (${assignedPosition.split(':')[0]})`);
|
|
421
576
|
const response = await this.cliOrchestrator.executeSingleCLI(currentAgent, confrontationalPrompt, confrontationalPrompt, {
|
|
422
577
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
423
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
424
578
|
timeout: (this.config.defaultTimeout || 60000) * 2,
|
|
425
579
|
models: models ? { [currentAgent]: models[currentAgent] } : undefined
|
|
426
580
|
});
|
|
427
581
|
if (response.success) {
|
|
428
582
|
debateContext.push(response);
|
|
429
|
-
|
|
583
|
+
if (response.output) {
|
|
584
|
+
fullDebateTranscript.get(currentAgent)?.push(response.output);
|
|
585
|
+
}
|
|
430
586
|
}
|
|
431
587
|
}
|
|
432
588
|
}
|
|
@@ -469,7 +625,9 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
469
625
|
if (!agentOutputs.has(response.agent)) {
|
|
470
626
|
agentOutputs.set(response.agent, []);
|
|
471
627
|
}
|
|
472
|
-
|
|
628
|
+
if (response.output) {
|
|
629
|
+
agentOutputs.get(response.agent)?.push(response.output);
|
|
630
|
+
}
|
|
473
631
|
});
|
|
474
632
|
synthesis += `## Key Points of Conflict\n\n`;
|
|
475
633
|
// Extract disagreements by looking for contradictory keywords
|
|
@@ -516,15 +674,14 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
516
674
|
}
|
|
517
675
|
return synthesis;
|
|
518
676
|
}
|
|
519
|
-
async executeBrutalistAnalysis(analysisType,
|
|
677
|
+
async executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, workingDirectory, preferredCLI, verbose, models, progressToken, sessionId, requestId) {
|
|
520
678
|
logger.info(`🏢 Starting brutalist analysis: ${analysisType}`);
|
|
521
|
-
logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI},
|
|
679
|
+
logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI}, primaryContent=${primaryContent}`);
|
|
522
680
|
logger.debug("Executing brutalist analysis", {
|
|
523
|
-
|
|
681
|
+
primaryContent,
|
|
524
682
|
analysisType,
|
|
525
683
|
systemPromptSpec,
|
|
526
684
|
workingDirectory,
|
|
527
|
-
enableSandbox,
|
|
528
685
|
preferredCLI
|
|
529
686
|
});
|
|
530
687
|
try {
|
|
@@ -535,16 +692,18 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
535
692
|
// Execute CLI agent analysis (single or multi-CLI based on preferences)
|
|
536
693
|
logger.info(`🔍 Executing brutalist analysis with timeout: ${this.config.defaultTimeout}ms`);
|
|
537
694
|
logger.info(`🔧 DEBUG: About to call cliOrchestrator.executeBrutalistAnalysis`);
|
|
538
|
-
const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType,
|
|
695
|
+
const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType, primaryContent, systemPromptSpec, context, {
|
|
539
696
|
workingDirectory: workingDirectory || this.config.workingDirectory,
|
|
540
|
-
sandbox: enableSandbox ?? this.config.enableSandbox,
|
|
541
697
|
timeout: this.config.defaultTimeout,
|
|
542
698
|
preferredCLI,
|
|
543
699
|
analysisType: analysisType,
|
|
544
700
|
models,
|
|
545
701
|
onStreamingEvent: this.handleStreamingEvent,
|
|
546
702
|
progressToken,
|
|
547
|
-
onProgress: progressToken
|
|
703
|
+
onProgress: progressToken && sessionId ?
|
|
704
|
+
(progress, total, message) => this.handleProgressUpdate(progressToken, progress, total, message, sessionId) : undefined,
|
|
705
|
+
sessionId,
|
|
706
|
+
requestId
|
|
548
707
|
});
|
|
549
708
|
logger.info(`🔧 DEBUG: cliOrchestrator.executeBrutalistAnalysis returned ${responses.length} responses`);
|
|
550
709
|
const successfulResponses = responses.filter(r => r.success);
|
|
@@ -558,7 +717,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
558
717
|
responses,
|
|
559
718
|
synthesis,
|
|
560
719
|
analysisType,
|
|
561
|
-
targetPath,
|
|
720
|
+
targetPath: primaryContent,
|
|
562
721
|
executionSummary: {
|
|
563
722
|
totalCLIs: responses.length,
|
|
564
723
|
successfulCLIs: successfulResponses.length,
|
|
@@ -682,39 +841,44 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
682
841
|
const totalTokens = estimateTokenCount(content);
|
|
683
842
|
// Format response with pagination info
|
|
684
843
|
let paginatedText = '';
|
|
685
|
-
// Add
|
|
844
|
+
// Add header
|
|
686
845
|
paginatedText += `# Brutalist Analysis Results\n\n`;
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
paginatedText += `**🔢 Token Estimate:** ~${chunkTokens.toLocaleString()} tokens (chunk) / ~${totalTokens.toLocaleString()} tokens (total)\n\n`;
|
|
692
|
-
if (pagination.hasMore) {
|
|
846
|
+
// Only show pagination metadata if pagination is actually needed
|
|
847
|
+
const needsPagination = pagination.totalChunks > 1 || pagination.hasMore;
|
|
848
|
+
if (needsPagination) {
|
|
849
|
+
paginatedText += `**📊 Pagination Status:** ${statusLine}\n`;
|
|
693
850
|
if (analysisId) {
|
|
694
|
-
paginatedText +=
|
|
851
|
+
paginatedText += `**🔑 Analysis ID:** ${analysisId}\n`;
|
|
695
852
|
}
|
|
696
|
-
|
|
697
|
-
|
|
853
|
+
paginatedText += `**🔢 Token Estimate:** ~${chunkTokens.toLocaleString()} tokens (chunk) / ~${totalTokens.toLocaleString()} tokens (total)\n\n`;
|
|
854
|
+
if (pagination.hasMore) {
|
|
855
|
+
if (analysisId) {
|
|
856
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`analysis_id: "${analysisId}", offset: ${endOffset}\`\n\n`;
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`offset: ${endOffset}\` for next chunk\n\n`;
|
|
860
|
+
}
|
|
698
861
|
}
|
|
699
862
|
}
|
|
700
863
|
paginatedText += `---\n\n`;
|
|
701
864
|
// Add the actual content chunk
|
|
702
865
|
paginatedText += chunkContent;
|
|
703
|
-
// Add footer
|
|
704
|
-
if (
|
|
866
|
+
// Add footer
|
|
867
|
+
if (needsPagination) {
|
|
705
868
|
paginatedText += `\n\n---\n\n`;
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
869
|
+
if (pagination.hasMore) {
|
|
870
|
+
paginatedText += `📖 **End of chunk ${pagination.chunkIndex}/${pagination.totalChunks}**\n`;
|
|
871
|
+
if (analysisId) {
|
|
872
|
+
paginatedText += `🔄 To continue: Include \`analysis_id: "${analysisId}"\` with \`offset: ${endOffset}\` in next request`;
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
paginatedText += `🔄 To continue: Use same tool with \`offset: ${endOffset}\``;
|
|
876
|
+
}
|
|
709
877
|
}
|
|
710
878
|
else {
|
|
711
|
-
paginatedText +=
|
|
879
|
+
paginatedText += `✅ **Complete analysis shown** (${content.length.toLocaleString()} characters total)`;
|
|
712
880
|
}
|
|
713
881
|
}
|
|
714
|
-
else {
|
|
715
|
-
paginatedText += `\n\n---\n\n`;
|
|
716
|
-
paginatedText += `✅ **Complete analysis shown** (${content.length.toLocaleString()} characters total)`;
|
|
717
|
-
}
|
|
718
882
|
// Add verbose execution details if requested
|
|
719
883
|
if (verbose && result.executionSummary) {
|
|
720
884
|
paginatedText += `\n\n### Execution Summary\n`;
|
|
@@ -742,7 +906,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
742
906
|
sanitizedMessage = "Analysis timed out - try reducing scope or increasing timeout";
|
|
743
907
|
}
|
|
744
908
|
else if (error.message.includes('ENOENT') || error.message.includes('no such file')) {
|
|
745
|
-
sanitizedMessage =
|
|
909
|
+
sanitizedMessage = `DEBUG: Target path not found - Original error: ${error.message}`;
|
|
746
910
|
}
|
|
747
911
|
else if (error.message.includes('EACCES') || error.message.includes('permission denied')) {
|
|
748
912
|
sanitizedMessage = "Permission denied - check file access";
|