@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 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-report.md (AI analysis) │
114
+ │ • copilot-insight.md (AI analysis) │
115
115
  └─────────────────────────────────────────────────┘
116
116
  ```
117
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qiaolei81/copilot-session-viewer",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Web UI for viewing GitHub Copilot CLI session logs",
5
5
  "author": "Lei Qiao <qiaolei81@gmail.com>",
6
6
  "license": "MIT",
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, insightLimiter, uploadLimiter } = require('./middleware/rateLimiting');
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', insightLimiter, insightController.generateInsight.bind(insightController));
80
- app.get('/session/:id/insight', insightLimiter, insightController.getInsightStatus.bind(insightController));
81
- app.delete('/session/:id/insight', insightLimiter, insightController.deleteInsight.bind(insightController));
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);
@@ -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) => req.path.startsWith('/public') // Skip static files
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 generation (stricter)
15
- const insightLimiter = rateLimit({
16
- windowMs: config.RATE_LIMIT_WINDOW_MS,
17
- max: config.RATE_LIMIT_MAX_REQUESTS,
18
- message: { error: 'Too many insight generation requests. Please try again later.' },
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
- insightLimiter,
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-report.md');
28
- const lockFile = path.join(sessionPath, 'insight-report.md.lock');
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-report.md.tmp');
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, one event per line) and generate a deep, actionable insight report.
205
+ return `Analyze this GitHub Copilot CLI session data (JSONL format) and produce a concise insight.
205
206
 
206
- CRITICAL: Output ONLY the analysis report. Do NOT include thinking blocks, reasoning steps, or meta-commentary about your analysis process. Go straight to insights.
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
- Focus on:
209
- 1. **Session Health Score** (0-100): Calculate based on success rate, completion rate, and performance
210
- - Red flags: error rate >50%, incomplete sub-agents, timeout patterns
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
- 3. **Performance Bottlenecks**:
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-report.md');
265
- const lockFile = path.join(sessionPath, 'insight-report.md.lock');
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-report.md');
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-report.md');
88
+ const insightReportFile = path.join(fullPath, 'copilot-insight.md');
89
89
 
90
90
  // Check if workspace.yaml exists
91
91
  if (!await fileExists(workspaceFile)) {