@qiaolei81/copilot-session-viewer 0.1.3 → 0.1.4
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/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/app.js +5 -5
- package/src/config/index.js +2 -2
- package/src/middleware/rateLimiting.js +28 -8
- package/src/services/insightService.js +20 -42
- package/src/services/sessionRepository.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.4] - 2026-02-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Rate limiting configuration for insight operations - resolved 429 "Too Many Requests" errors
|
|
12
|
+
- Removed rate limiting from insight status checks (GET requests) - status checks are now unlimited
|
|
13
|
+
- Improved rate limiting differentiation: strict for generation (POST), lenient for access (DELETE)
|
|
14
|
+
- Fixed "Age: NaNs" timestamp display issue in insight generation progress
|
|
15
|
+
- Added missing `ageMs` calculation to backend insight service responses
|
|
16
|
+
- ESLint configuration migration from deprecated `.eslintignore` to modern flat config
|
|
17
|
+
- Minimal `.npmignore` configuration for optimized package publishing (82% size reduction)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Insight output file renamed from `insight-report.md` to `copilot-insight.md`
|
|
21
|
+
- Insight prompt rewritten to enforce ≤500 character output (down from ~2000 words)
|
|
22
|
+
- Insight now focuses on three essentials: health score, top issue, key recommendation
|
|
23
|
+
- Insight generation rate limiting: 3 requests per 5 minutes (more user-friendly window)
|
|
24
|
+
- Insight access operations: 50 requests per minute (very lenient for status checks)
|
|
25
|
+
- Package size optimized from 298kB to 52kB for npm publishing
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
- Deprecated `.eslintignore` file in favor of `eslint.config.mjs` ignores property
|
|
29
|
+
- Verbose `.npmignore` entries - simplified to essential exclusions only
|
|
30
|
+
|
|
8
31
|
## [0.1.3] - 2026-02-16
|
|
9
32
|
|
|
10
33
|
### Added
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ npx @qiaolei81/copilot-session-viewer
|
|
|
111
111
|
│ Data Layer (~/.copilot/session-state/) │
|
|
112
112
|
│ • events.jsonl (event streams) │
|
|
113
113
|
│ • workspace.yaml (metadata) │
|
|
114
|
-
│ • insight
|
|
114
|
+
│ • copilot-insight.md (AI analysis) │
|
|
115
115
|
└─────────────────────────────────────────────────┘
|
|
116
116
|
```
|
|
117
117
|
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -7,7 +7,7 @@ const helmet = require('helmet');
|
|
|
7
7
|
const config = require('./config');
|
|
8
8
|
|
|
9
9
|
// Middleware
|
|
10
|
-
const { globalLimiter,
|
|
10
|
+
const { globalLimiter, insightGenerationLimiter, insightAccessLimiter, uploadLimiter } = require('./middleware/rateLimiting');
|
|
11
11
|
const { requestTimeout, developmentCors, errorHandler, notFoundHandler } = require('./middleware/common');
|
|
12
12
|
|
|
13
13
|
// Controllers
|
|
@@ -75,10 +75,10 @@ function createApp(options = {}) {
|
|
|
75
75
|
uploadController.importSession.bind(uploadController)
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
// Insight routes with rate limiting
|
|
79
|
-
app.post('/session/:id/insight',
|
|
80
|
-
app.get('/session/:id/insight',
|
|
81
|
-
app.delete('/session/:id/insight',
|
|
78
|
+
// Insight routes with appropriate rate limiting
|
|
79
|
+
app.post('/session/:id/insight', insightGenerationLimiter, insightController.generateInsight.bind(insightController));
|
|
80
|
+
app.get('/session/:id/insight', insightController.getInsightStatus.bind(insightController)); // Remove rate limiting for GET
|
|
81
|
+
app.delete('/session/:id/insight', insightAccessLimiter, insightController.deleteInsight.bind(insightController));
|
|
82
82
|
|
|
83
83
|
// Upload rate limiting
|
|
84
84
|
app.use('/session/import', uploadLimiter);
|
package/src/config/index.js
CHANGED
|
@@ -20,8 +20,8 @@ module.exports = {
|
|
|
20
20
|
// Session Repository
|
|
21
21
|
SESSION_CACHE_TTL_MS: 30 * 1000, // 30 seconds
|
|
22
22
|
|
|
23
|
-
// Request Limits
|
|
23
|
+
// Request Limits - More lenient for better UX
|
|
24
24
|
RATE_LIMIT_WINDOW_MS: 15 * 60 * 1000, // 15 minutes
|
|
25
|
-
RATE_LIMIT_MAX_REQUESTS: 5
|
|
25
|
+
RATE_LIMIT_MAX_REQUESTS: 15, // Increased from 5 to 15 for better UX
|
|
26
26
|
REQUEST_TIMEOUT_MS: 30 * 1000 // 30 seconds
|
|
27
27
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const rateLimit = require('express-rate-limit');
|
|
2
|
-
const config = require('../config');
|
|
3
2
|
|
|
4
3
|
// Global rate limiting for all routes
|
|
5
4
|
const globalLimiter = rateLimit({
|
|
@@ -8,14 +7,34 @@ const globalLimiter = rateLimit({
|
|
|
8
7
|
message: { error: 'Too many requests. Please try again later.' },
|
|
9
8
|
standardHeaders: true,
|
|
10
9
|
legacyHeaders: false,
|
|
11
|
-
skip: (req) =>
|
|
10
|
+
skip: (req) => {
|
|
11
|
+
// Skip static files
|
|
12
|
+
if (req.path.startsWith('/public')) return true;
|
|
13
|
+
|
|
14
|
+
// Skip insight status checks (GET requests)
|
|
15
|
+
if (req.method === 'GET' && req.path.includes('/insight')) return true;
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Rate limiting for insight generation (stricter for POST)
|
|
22
|
+
const insightGenerationLimiter = rateLimit({
|
|
23
|
+
windowMs: 5 * 60 * 1000, // 5 minutes (shorter window)
|
|
24
|
+
max: 3, // 3 generations per 5-minute window (expensive operations)
|
|
25
|
+
message: {
|
|
26
|
+
error: 'Too many insight generation requests. Please wait 5 minutes before generating another insight.',
|
|
27
|
+
retryAfter: 5 * 60 // 5 minutes in seconds
|
|
28
|
+
},
|
|
29
|
+
standardHeaders: true,
|
|
30
|
+
legacyHeaders: false
|
|
12
31
|
});
|
|
13
32
|
|
|
14
|
-
// Rate limiting for insight
|
|
15
|
-
const
|
|
16
|
-
windowMs:
|
|
17
|
-
max:
|
|
18
|
-
message: { error: 'Too many insight
|
|
33
|
+
// Rate limiting for insight status/retrieval (very lenient for GET/DELETE)
|
|
34
|
+
const insightAccessLimiter = rateLimit({
|
|
35
|
+
windowMs: 1 * 60 * 1000, // 1 minute (shorter window)
|
|
36
|
+
max: 50, // 50 requests per minute (very lenient)
|
|
37
|
+
message: { error: 'Too many insight requests. Please try again in a minute.' },
|
|
19
38
|
standardHeaders: true,
|
|
20
39
|
legacyHeaders: false
|
|
21
40
|
});
|
|
@@ -31,6 +50,7 @@ const uploadLimiter = rateLimit({
|
|
|
31
50
|
|
|
32
51
|
module.exports = {
|
|
33
52
|
globalLimiter,
|
|
34
|
-
|
|
53
|
+
insightGenerationLimiter,
|
|
54
|
+
insightAccessLimiter,
|
|
35
55
|
uploadLimiter
|
|
36
56
|
};
|
|
@@ -24,8 +24,8 @@ class InsightService {
|
|
|
24
24
|
*/
|
|
25
25
|
async generateInsight(sessionId, forceRegenerate = false) {
|
|
26
26
|
const sessionPath = path.join(this.sessionDir, sessionId);
|
|
27
|
-
const insightFile = path.join(sessionPath, 'insight
|
|
28
|
-
const lockFile = path.join(sessionPath, 'insight
|
|
27
|
+
const insightFile = path.join(sessionPath, 'copilot-insight.md');
|
|
28
|
+
const lockFile = path.join(sessionPath, 'copilot-insight.md.lock');
|
|
29
29
|
const eventsFile = path.join(sessionPath, 'events.jsonl');
|
|
30
30
|
|
|
31
31
|
// Check if complete insight exists
|
|
@@ -65,7 +65,8 @@ class InsightService {
|
|
|
65
65
|
status: 'generating',
|
|
66
66
|
report: '# Generating Copilot Insight...\n\nAnother request is currently generating this insight. Please wait.',
|
|
67
67
|
startedAt: lockStats.birthtime,
|
|
68
|
-
lastUpdate: lockStats.mtime
|
|
68
|
+
lastUpdate: lockStats.mtime,
|
|
69
|
+
ageMs: Date.now() - lockStats.birthtime.getTime()
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -123,7 +124,7 @@ class InsightService {
|
|
|
123
124
|
await fs.mkdir(tmpDir, { recursive: true });
|
|
124
125
|
|
|
125
126
|
const prompt = this._buildPrompt();
|
|
126
|
-
const outputFile = path.join(sessionPath, 'insight
|
|
127
|
+
const outputFile = path.join(sessionPath, 'copilot-insight.md.tmp');
|
|
127
128
|
|
|
128
129
|
// Spawn copilot directly (no shell)
|
|
129
130
|
const copilotPath = 'copilot';
|
|
@@ -201,42 +202,18 @@ class InsightService {
|
|
|
201
202
|
* @private
|
|
202
203
|
*/
|
|
203
204
|
_buildPrompt() {
|
|
204
|
-
return `Analyze this GitHub Copilot CLI session data (JSONL format
|
|
205
|
+
return `Analyze this GitHub Copilot CLI session data (JSONL format) and produce a concise insight.
|
|
205
206
|
|
|
206
|
-
CRITICAL:
|
|
207
|
+
CRITICAL CONSTRAINTS:
|
|
208
|
+
- Your ENTIRE output must be UNDER 500 characters (not words — characters, including spaces and punctuation).
|
|
209
|
+
- Output ONLY the analysis. No thinking blocks, no meta-commentary.
|
|
207
210
|
|
|
208
|
-
|
|
209
|
-
1. **
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
2. **Critical Issues** (if any):
|
|
213
|
-
- What went wrong and why (root cause analysis)
|
|
214
|
-
- Impact on user workflow
|
|
215
|
-
- Specific failing patterns (e.g., "all 'create' calls missing file_text parameter")
|
|
211
|
+
Include:
|
|
212
|
+
1. **Health Score** (0-100) based on success/error rates
|
|
213
|
+
2. **Top Issue**: The single most impactful problem with root cause
|
|
214
|
+
3. **Key Recommendation**: One specific, actionable improvement
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
- Slowest operations with timing data
|
|
219
|
-
- Where LLM is spending most time
|
|
220
|
-
- Tool execution delays vs LLM thinking time
|
|
221
|
-
|
|
222
|
-
4. **Sub-Agent Effectiveness**:
|
|
223
|
-
- Which sub-agents succeeded/failed and why
|
|
224
|
-
- Completion patterns and failure points
|
|
225
|
-
- Resource utilization (tool calls per sub-agent)
|
|
226
|
-
|
|
227
|
-
5. **Tool Usage Intelligence**:
|
|
228
|
-
- Most/least used tools
|
|
229
|
-
- Error patterns per tool type
|
|
230
|
-
- Unused but potentially helpful tools
|
|
231
|
-
|
|
232
|
-
6. **Workflow Recommendations**:
|
|
233
|
-
- Actionable improvements (specific, not generic)
|
|
234
|
-
- Configuration tuning suggestions
|
|
235
|
-
- Anti-patterns detected
|
|
236
|
-
|
|
237
|
-
Use data-driven language with specific numbers. Be critical, not descriptive. Focus on "why" and "what to do" rather than "what happened".
|
|
238
|
-
|
|
239
|
-
Output in clean Markdown with ## headers. Keep it concise but insightful (<2000 words).`;
|
|
216
|
+
Be data-driven with specific numbers. No filler or generic advice.`;
|
|
240
217
|
}
|
|
241
218
|
|
|
242
219
|
/**
|
|
@@ -261,8 +238,8 @@ Output in clean Markdown with ## headers. Keep it concise but insightful (<2000
|
|
|
261
238
|
*/
|
|
262
239
|
async getInsightStatus(sessionId) {
|
|
263
240
|
const sessionPath = path.join(this.sessionDir, sessionId);
|
|
264
|
-
const insightFile = path.join(sessionPath, 'insight
|
|
265
|
-
const lockFile = path.join(sessionPath, 'insight
|
|
241
|
+
const insightFile = path.join(sessionPath, 'copilot-insight.md');
|
|
242
|
+
const lockFile = path.join(sessionPath, 'copilot-insight.md.lock');
|
|
266
243
|
|
|
267
244
|
try {
|
|
268
245
|
const report = await fs.readFile(insightFile, 'utf-8');
|
|
@@ -280,7 +257,8 @@ Output in clean Markdown with ## headers. Keep it concise but insightful (<2000
|
|
|
280
257
|
return {
|
|
281
258
|
status: 'generating',
|
|
282
259
|
startedAt: stats.birthtime,
|
|
283
|
-
lastUpdate: stats.mtime
|
|
260
|
+
lastUpdate: stats.mtime,
|
|
261
|
+
ageMs: Date.now() - stats.birthtime.getTime()
|
|
284
262
|
};
|
|
285
263
|
} catch (_lockErr) {
|
|
286
264
|
return { status: 'not_started' };
|
|
@@ -293,8 +271,8 @@ Output in clean Markdown with ## headers. Keep it concise but insightful (<2000
|
|
|
293
271
|
*/
|
|
294
272
|
async deleteInsight(sessionId) {
|
|
295
273
|
const sessionPath = path.join(this.sessionDir, sessionId);
|
|
296
|
-
const insightFile = path.join(sessionPath, 'insight
|
|
297
|
-
|
|
274
|
+
const insightFile = path.join(sessionPath, 'copilot-insight.md');
|
|
275
|
+
|
|
298
276
|
try {
|
|
299
277
|
await fs.unlink(insightFile);
|
|
300
278
|
return { success: true };
|
|
@@ -85,7 +85,7 @@ class SessionRepository {
|
|
|
85
85
|
const workspaceFile = path.join(fullPath, 'workspace.yaml');
|
|
86
86
|
const eventsFile = path.join(fullPath, 'events.jsonl');
|
|
87
87
|
const importedMarkerFile = path.join(fullPath, '.imported');
|
|
88
|
-
const insightReportFile = path.join(fullPath, 'insight
|
|
88
|
+
const insightReportFile = path.join(fullPath, 'copilot-insight.md');
|
|
89
89
|
|
|
90
90
|
// Check if workspace.yaml exists
|
|
91
91
|
if (!await fileExists(workspaceFile)) {
|