copilot-metrics 0.1.1 → 0.1.2

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
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 - 2026-05-31
4
+
5
+ ### Fixed
6
+
7
+ - Reports now import Copilot CLI session-state `events.jsonl` files by default, so token statistics are collected after `init` and hooks install without requiring users to export telemetry environment variables.
8
+ - Copilot session-state imports persist only shutdown usage records, while using prompt-bearing session events in memory for label extraction and context.
9
+ - Hook-only report diagnostics now stay quiet when token-bearing Copilot session-state usage is available.
10
+
3
11
  ## 0.1.1 - 2026-05-30
4
12
 
5
13
  ### Added
package/README.md CHANGED
@@ -9,8 +9,8 @@ Costs are estimates, not official billing records. GitHub billing remains the so
9
9
  From npm:
10
10
 
11
11
  ```bash
12
- npx copilot-metrics@0.1.1 --help
13
- npx copilot-metrics@0.1.1 init
12
+ npx copilot-metrics@0.1.2 --help
13
+ npx copilot-metrics@0.1.2 init
14
14
  ```
15
15
 
16
16
  From this checkout:
@@ -38,24 +38,28 @@ export COPILOT_METRICS_HOME=/path/to/copilot-metrics-data
38
38
  Useful commands:
39
39
 
40
40
  ```bash
41
- npx copilot-metrics@0.1.1 init
42
- npx copilot-metrics@0.1.1 paths --json
41
+ npx copilot-metrics@0.1.2 init
42
+ npx copilot-metrics@0.1.2 paths --json
43
43
  ```
44
44
 
45
45
  ## Configure Telemetry
46
46
 
47
+ For Copilot CLI, `init` plus hooks are enough for local token reporting. Reports import Copilot's local session-state `events.jsonl` files and extract shutdown usage totals without requiring telemetry environment variables.
48
+
47
49
  Print VS Code Insiders Copilot Chat OpenTelemetry settings:
48
50
 
49
51
  ```bash
50
- npx copilot-metrics@0.1.1 setup vscode
52
+ npx copilot-metrics@0.1.2 setup vscode
51
53
  ```
52
54
 
53
55
  Print Copilot CLI OpenTelemetry environment exports:
54
56
 
55
57
  ```bash
56
- npx copilot-metrics@0.1.1 setup copilot-cli
58
+ npx copilot-metrics@0.1.2 setup copilot-cli
57
59
  ```
58
60
 
61
+ This is optional. Use it only when you also want Copilot CLI OTel JSONL output.
62
+
59
63
  Content capture is disabled by default. Do not enable richer prompt capture unless you explicitly accept the privacy tradeoff.
60
64
 
61
65
  ## Configure Hooks
@@ -63,51 +67,52 @@ Content capture is disabled by default. Do not enable richer prompt capture unle
63
67
  Preview repo-local hook config. The default `--surface both` emits the Copilot CLI lower camel case hook format:
64
68
 
65
69
  ```bash
66
- npx copilot-metrics@0.1.1 hooks preview --scope local --surface both
70
+ npx copilot-metrics@0.1.2 hooks preview --scope local --surface both
67
71
  ```
68
72
 
69
73
  Install repo-local or user-global hook config:
70
74
 
71
75
  ```bash
72
- npx copilot-metrics@0.1.1 hooks install --scope local --surface both
73
- npx copilot-metrics@0.1.1 hooks install --scope global --surface both
76
+ npx copilot-metrics@0.1.2 hooks install --scope local --surface both
77
+ npx copilot-metrics@0.1.2 hooks install --scope global --surface both
74
78
  ```
75
79
 
76
80
  Local install writes `.github/hooks/copilot-metrics.json`. Global install updates `~/.copilot/settings.json` idempotently, replacing prior `copilot-metrics` hook entries while preserving other settings and hooks. Use `--surface vscode` for VS Code-only PascalCase events or `--surface copilot-cli` for CLI-native lower camel case events. The hook logger writes redacted JSONL metadata to the central data directory. It extracts Jira-style labels such as `DEMO-12345` from safe metadata and does not store full prompt text by default.
77
81
 
78
82
  ## Import Telemetry
79
83
 
80
- Initialize the local SQLite store and import JSONL files:
84
+ Initialize the local SQLite store and import JSONL files manually:
81
85
 
82
86
  ```bash
83
- npx copilot-metrics@0.1.1 store init
84
- npx copilot-metrics@0.1.1 import --source vscode --file ~/.local/share/copilot-metrics/telemetry/vscode-copilot-otel.jsonl
85
- npx copilot-metrics@0.1.1 import --source copilot-cli --file ~/.local/share/copilot-metrics/telemetry/copilot-cli-otel.jsonl
86
- npx copilot-metrics@0.1.1 import --source hooks --file ~/.local/share/copilot-metrics/hooks/copilot-hooks.jsonl
87
+ npx copilot-metrics@0.1.2 store init
88
+ npx copilot-metrics@0.1.2 import --source vscode --file ~/.local/share/copilot-metrics/telemetry/vscode-copilot-otel.jsonl
89
+ npx copilot-metrics@0.1.2 import --source copilot-cli --file ~/.local/share/copilot-metrics/telemetry/copilot-cli-otel.jsonl
90
+ npx copilot-metrics@0.1.2 import --source copilot-session --file ~/.copilot/session-state/<session-id>/events.jsonl
91
+ npx copilot-metrics@0.1.2 import --source hooks --file ~/.local/share/copilot-metrics/hooks/copilot-hooks.jsonl
87
92
  ```
88
93
 
89
- Imports persist raw records, normalized LLM usage records, hook events, label evidence, and import warnings. Re-importing the same JSONL rows is idempotent.
94
+ Imports persist raw records, normalized LLM usage records, hook events, label evidence, and import warnings. Re-importing the same JSONL rows is idempotent. For Copilot session-state files, only shutdown usage rows are persisted; prompt-bearing session events are used in memory for label extraction and context and are not stored as raw records.
90
95
 
91
96
  ## Reports
92
97
 
93
98
  Run local reports from the SQLite store:
94
99
 
95
100
  ```bash
96
- npx copilot-metrics@0.1.1 report labels
97
- npx copilot-metrics@0.1.1 report label DEMO-12345
98
- npx copilot-metrics@0.1.1 report label DEMO-12345 --detail
99
- npx copilot-metrics@0.1.1 report models
100
- npx copilot-metrics@0.1.1 report repos
101
- npx copilot-metrics@0.1.1 report unattributed
101
+ npx copilot-metrics@0.1.2 report labels
102
+ npx copilot-metrics@0.1.2 report label DEMO-12345
103
+ npx copilot-metrics@0.1.2 report label DEMO-12345 --detail
104
+ npx copilot-metrics@0.1.2 report models
105
+ npx copilot-metrics@0.1.2 report repos
106
+ npx copilot-metrics@0.1.2 report unattributed
102
107
  ```
103
108
 
104
109
  Every report supports `--json`:
105
110
 
106
111
  ```bash
107
- npx copilot-metrics@0.1.1 report labels --json
112
+ npx copilot-metrics@0.1.2 report labels --json
108
113
  ```
109
114
 
110
- Report commands automatically import newly appended configured VS Code, Copilot CLI, and hook JSONL files before querying. Label reports include input, output, cache read, cache creation, and reasoning token totals. Labels seen only in hooks remain visible as hook-only evidence with zero usage records, so attribution hints do not imply token-bearing usage.
115
+ Report commands automatically import newly appended configured VS Code OTel, optional Copilot CLI OTel, Copilot CLI session-state, and hook JSONL files before querying. Label reports include input, output, cache read, cache creation, and reasoning token totals. Labels seen only in hooks remain visible as hook-only evidence with zero usage records, so attribution hints do not imply token-bearing usage.
111
116
 
112
117
  ## Attribution Model
113
118
 
@@ -170,7 +175,7 @@ The manual prompt performs one harmless tool call so Copilot CLI hook execution
170
175
  ## Current Limits
171
176
 
172
177
  - Costs are estimates, not official billing records.
173
- - Official GitHub usage report reconciliation is not included in `0.1.1`.
174
- - Local OTLP collector mode is not included in `0.1.1`.
175
- - Richer prompt/content capture and redaction controls are not included in `0.1.1`.
178
+ - Official GitHub usage report reconciliation is not included in `0.1.2`.
179
+ - Local OTLP collector mode is not included in `0.1.2`.
180
+ - Richer prompt/content capture and redaction controls are not included in `0.1.2`.
176
181
  - Dashboard views are deferred until the CLI/query model proves useful.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-metrics",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Local-first Copilot usage telemetry setup and reporting tools.",
5
5
  "type": "commonjs",
6
6
  "homepage": "https://github.com/nnexai/copilot-metrics#readme",
package/src/cli.js CHANGED
@@ -34,6 +34,10 @@ function parseFlags(args) {
34
34
  const rest = [];
35
35
  for (let i = 0; i < args.length; i += 1) {
36
36
  const arg = args[i];
37
+ if (arg === '--') {
38
+ rest.push(...args.slice(i + 1));
39
+ break;
40
+ }
37
41
  if (!arg.startsWith('--')) {
38
42
  rest.push(arg);
39
43
  continue;
@@ -72,7 +76,7 @@ Usage:
72
76
  copilot-metrics hooks install [--scope local|global] [--surface both|vscode|copilot-cli] [--json]
73
77
  copilot-metrics hook-log --event <name>
74
78
  copilot-metrics store init [--json]
75
- copilot-metrics import --source vscode|copilot-cli|hooks --file <path> [--json]
79
+ copilot-metrics import --source vscode|copilot-cli|copilot-session|hooks --file <path> [--json]
76
80
  copilot-metrics report labels [--json]
77
81
  copilot-metrics report label <id> [--detail] [--json]
78
82
  copilot-metrics report models [--json]
@@ -112,10 +116,55 @@ function formatCopilotCli(env) {
112
116
  'Export these variables before running Copilot CLI:',
113
117
  shellExports(env),
114
118
  '',
119
+ 'This is optional. Copilot CLI session-state usage is imported by reports without these exports.',
120
+ '',
115
121
  'Content capture is disabled by default.',
116
122
  ].join('\n');
117
123
  }
118
124
 
125
+ function telemetryDiagnostics(importResults) {
126
+ const hookResult = importResults.find((result) => result.source === 'hooks' && result.raw_records > 0);
127
+ if (!hookResult) return [];
128
+ const sessionUsage = importResults.find((result) => result.source === 'copilot-session' && result.raw_records > 0);
129
+ if (sessionUsage) return [];
130
+
131
+ const cliTelemetry = importResults.find((result) => result.source === 'copilot-cli');
132
+ if (!cliTelemetry) return [];
133
+
134
+ if (cliTelemetry.skipped && cliTelemetry.reason === 'missing_file') {
135
+ return [{
136
+ code: 'missing_copilot_cli_otel',
137
+ message: `Hook evidence was found, but no token-bearing Copilot session-state or OTel usage was imported. Check that ${cliTelemetry.file} exists for optional OTel data, or that Copilot session-state files are available under the configured COPILOT_HOME.`,
138
+ }];
139
+ }
140
+
141
+ if (cliTelemetry.raw_records === 0) {
142
+ return [{
143
+ code: 'empty_copilot_cli_otel',
144
+ message: `Hook evidence was found, but Copilot CLI token telemetry is empty at ${cliTelemetry.file}. Run another Copilot session with OTel enabled.`,
145
+ }];
146
+ }
147
+
148
+ if (cliTelemetry.usage_records === 0) {
149
+ return [{
150
+ code: 'no_copilot_cli_usage_records',
151
+ message: `Copilot CLI telemetry was found at ${cliTelemetry.file}, but no token-bearing LLM spans were normalized from it.`,
152
+ }];
153
+ }
154
+
155
+ return [];
156
+ }
157
+
158
+ function appendDiagnostics(output, diagnostics) {
159
+ if (diagnostics.length === 0) return output;
160
+ return [
161
+ output,
162
+ '',
163
+ 'Diagnostics:',
164
+ ...diagnostics.map((diagnostic) => `- ${diagnostic.message}`),
165
+ ].join('\n');
166
+ }
167
+
119
168
  async function main(args, io) {
120
169
  const { flags, rest } = parseFlags(args);
121
170
  const json = flags.json === true;
@@ -213,8 +262,8 @@ async function main(args, io) {
213
262
  : source === 'hooks'
214
263
  ? paths.hookEventsJsonl
215
264
  : null);
216
- if (!['vscode', 'copilot-cli', 'hooks'].includes(source)) {
217
- throw new Error('import requires --source vscode|copilot-cli|hooks');
265
+ if (!['vscode', 'copilot-cli', 'copilot-session', 'hooks'].includes(source)) {
266
+ throw new Error('import requires --source vscode|copilot-cli|copilot-session|hooks');
218
267
  }
219
268
  if (!file) throw new Error('import requires --file <path>');
220
269
  ensureDataDirs(paths);
@@ -237,14 +286,15 @@ async function main(args, io) {
237
286
 
238
287
  if (command === 'report') {
239
288
  ensureDataDirs(paths);
240
- await autoImportConfiguredSources(paths, {
289
+ const imports = await autoImportConfiguredSources(paths, {
241
290
  cwd: io.cwd,
242
291
  extractors: loadConfiguredExtractors(paths.configJson, io.cwd),
243
292
  });
293
+ const diagnostics = telemetryDiagnostics(imports);
244
294
 
245
295
  if (subcommand === 'labels') {
246
296
  const rows = await labelOverview(paths.usageDb);
247
- writeOutput(io.stdout, json ? { labels: rows } : formatLabels(rows), json);
297
+ writeOutput(io.stdout, json ? { labels: rows, diagnostics } : appendDiagnostics(formatLabels(rows), diagnostics), json);
248
298
  return;
249
299
  }
250
300
  if (subcommand === 'label') {
@@ -253,10 +303,10 @@ async function main(args, io) {
253
303
  const summary = await labelSummary(paths.usageDb, label);
254
304
  if (flags.detail === true) {
255
305
  const details = await labelDetails(paths.usageDb, label);
256
- writeOutput(io.stdout, json ? { label: summary, details } : formatLabelSummary(summary, details), json);
306
+ writeOutput(io.stdout, json ? { label: summary, details, diagnostics } : appendDiagnostics(formatLabelSummary(summary, details), diagnostics), json);
257
307
  return;
258
308
  }
259
- writeOutput(io.stdout, json ? { label: summary } : formatLabelSummary(summary), json);
309
+ writeOutput(io.stdout, json ? { label: summary, diagnostics } : appendDiagnostics(formatLabelSummary(summary), diagnostics), json);
260
310
  return;
261
311
  }
262
312
  if (subcommand === 'models') {
@@ -291,4 +341,5 @@ module.exports = {
291
341
  main,
292
342
  parseFlags,
293
343
  helpText,
344
+ telemetryDiagnostics,
294
345
  };
package/src/ingest.js CHANGED
@@ -4,7 +4,7 @@ const crypto = require('node:crypto');
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
  const { readJsonl } = require('./jsonl');
7
- const { normalizePayload, normalizeHookEvent } = require('./otel');
7
+ const { normalizePayload, normalizeHookEvent, normalizeCopilotSessionEvents } = require('./otel');
8
8
  const { estimateCost, PRICING_VERSION } = require('./pricing');
9
9
  const { existingRawFingerprints, insertImport } = require('./sqlite-store');
10
10
  const { attachUsageLabelEvidence, attachHookLabelEvidence } = require('./labels');
@@ -38,6 +38,10 @@ function rawFingerprint(source, file, record) {
38
38
  .digest('hex');
39
39
  }
40
40
 
41
+ function isCopilotSessionUsageRecord(record) {
42
+ return record.value && record.value.type === 'session.shutdown';
43
+ }
44
+
41
45
  async function ingestFile(options) {
42
46
  const { dbPath, file, source } = options;
43
47
  const parsed = readJsonl(file);
@@ -47,23 +51,30 @@ async function ingestFile(options) {
47
51
  ...record,
48
52
  raw_fingerprint: rawFingerprint(source, sourceFile, record),
49
53
  }));
54
+ const importableRecords = source === 'copilot-session'
55
+ ? parsedRecords.filter(isCopilotSessionUsageRecord)
56
+ : parsedRecords;
50
57
  const existing = await existingRawFingerprints(
51
58
  dbPath,
52
59
  source,
53
60
  sourceFile,
54
- parsedRecords.map((record) => record.raw_fingerprint),
61
+ importableRecords.map((record) => record.raw_fingerprint),
55
62
  );
56
- const newRecords = parsedRecords.filter((record) => !existing.has(record.raw_fingerprint));
63
+ const newRecords = importableRecords.filter((record) => !existing.has(record.raw_fingerprint));
57
64
  const usageRecords = [];
58
65
  const hookEvents = [];
59
66
 
60
- for (const record of newRecords) {
61
- if (source === 'hooks') {
62
- const event = normalizeHookEvent(record.value, source, record.line);
63
- if (event) hookEvents.push(event);
64
- continue;
67
+ if (source === 'copilot-session') {
68
+ usageRecords.push(...normalizeCopilotSessionEvents(newRecords, parsedRecords));
69
+ } else {
70
+ for (const record of newRecords) {
71
+ if (source === 'hooks') {
72
+ const event = normalizeHookEvent(record.value, source, record.line);
73
+ if (event) hookEvents.push(event);
74
+ continue;
75
+ }
76
+ usageRecords.push(...normalizePayload(record.value, source, record.line));
65
77
  }
66
- usageRecords.push(...normalizePayload(record.value, source, record.line));
67
78
  }
68
79
 
69
80
  const extractorOptions = { extractors: options.extractors || [] };
@@ -85,9 +96,9 @@ async function ingestFile(options) {
85
96
  source,
86
97
  file,
87
98
  dbPath,
88
- raw_records: parsed.records.length,
99
+ raw_records: importableRecords.length,
89
100
  new_raw_records: newRecords.length,
90
- skipped_existing_records: parsed.records.length - newRecords.length,
101
+ skipped_existing_records: importableRecords.length - newRecords.length,
91
102
  usage_records: enrichedUsage.length,
92
103
  hook_events: enrichedHooks.length,
93
104
  label_evidence: enrichedUsage.reduce((sum, usage) => sum + (usage.label_evidence || []).length, 0)
@@ -105,6 +116,7 @@ function configuredSourceFiles(paths, config = {}) {
105
116
  { source: 'hooks', file: sourceConfig.vscode?.hooks || paths.hookEventsJsonl },
106
117
  { source: 'copilot-cli', file: sourceConfig.copilotCli?.telemetry || telemetryConfig.copilotCli || paths.copilotCliOtelJsonl },
107
118
  { source: 'hooks', file: sourceConfig.copilotCli?.hooks || paths.hookEventsJsonl },
119
+ ...discoverCopilotSessionFiles(sourceConfig.copilotCli?.sessions || paths.copilotSessionStateDir),
108
120
  ];
109
121
  const seen = new Set();
110
122
  return files
@@ -118,6 +130,15 @@ function configuredSourceFiles(paths, config = {}) {
118
130
  });
119
131
  }
120
132
 
133
+ function discoverCopilotSessionFiles(sessionStateDir) {
134
+ if (!sessionStateDir || !fs.existsSync(sessionStateDir)) return [];
135
+ return fs.readdirSync(sessionStateDir, { withFileTypes: true })
136
+ .filter((entry) => entry.isDirectory())
137
+ .map((entry) => path.join(sessionStateDir, entry.name, 'events.jsonl'))
138
+ .filter((file) => fs.existsSync(file))
139
+ .map((file) => ({ source: 'copilot-session', file }));
140
+ }
141
+
121
142
  function readConfig(configJson) {
122
143
  if (!fs.existsSync(configJson)) return {};
123
144
  return JSON.parse(fs.readFileSync(configJson, 'utf8'));
@@ -145,5 +166,6 @@ async function autoImportConfiguredSources(paths, options = {}) {
145
166
  module.exports = {
146
167
  autoImportConfiguredSources,
147
168
  configuredSourceFiles,
169
+ discoverCopilotSessionFiles,
148
170
  ingestFile,
149
171
  };
package/src/otel.js CHANGED
@@ -29,6 +29,23 @@ function number(attrs, keys) {
29
29
  return Number.isFinite(parsed) ? parsed : 0;
30
30
  }
31
31
 
32
+ const LABEL_RE = /\b[A-Z][A-Z0-9]+-\d+\b/g;
33
+
34
+ function collectLabels(value, labels = new Set()) {
35
+ if (typeof value === 'string') {
36
+ for (const match of value.matchAll(LABEL_RE)) labels.add(match[0]);
37
+ return labels;
38
+ }
39
+ if (Array.isArray(value)) {
40
+ for (const item of value) collectLabels(item, labels);
41
+ return labels;
42
+ }
43
+ if (value && typeof value === 'object') {
44
+ for (const item of Object.values(value)) collectLabels(item, labels);
45
+ }
46
+ return labels;
47
+ }
48
+
32
49
  function flattenSpans(payload) {
33
50
  if (!payload || typeof payload !== 'object') return [];
34
51
  if (payload.name || payload.attributes || payload.spanId) return [payload];
@@ -102,6 +119,76 @@ function normalizePayload(payload, source, rawLine) {
102
119
  .filter(Boolean);
103
120
  }
104
121
 
122
+ function normalizeCopilotSessionEvents(newRecords, allRecords) {
123
+ const session = {
124
+ labels: new Set(),
125
+ id: null,
126
+ cwd: null,
127
+ repo: null,
128
+ branch: null,
129
+ commit: null,
130
+ conversationId: null,
131
+ };
132
+
133
+ for (const record of allRecords) {
134
+ const event = record.value;
135
+ if (!event || typeof event !== 'object') continue;
136
+ if (event.type === 'session.start') {
137
+ const data = event.data || {};
138
+ const context = data.context || {};
139
+ session.id = data.sessionId || session.id;
140
+ session.cwd = context.cwd || session.cwd;
141
+ session.repo = context.gitRoot || context.repository || session.repo;
142
+ session.branch = context.branch || session.branch;
143
+ session.commit = context.headCommit || session.commit;
144
+ collectLabels(context, session.labels);
145
+ }
146
+ if (event.type === 'hook.start') {
147
+ collectLabels(event.data && event.data.input, session.labels);
148
+ }
149
+ if (event.type === 'assistant.message') {
150
+ const data = event.data || {};
151
+ session.conversationId = data.interactionId || session.conversationId;
152
+ collectLabels(data.content, session.labels);
153
+ }
154
+ }
155
+
156
+ const records = [];
157
+ for (const record of newRecords) {
158
+ const event = record.value;
159
+ if (!event || event.type !== 'session.shutdown') continue;
160
+ const data = event.data || {};
161
+ const modelMetrics = data.modelMetrics || {};
162
+ for (const [model, metrics] of Object.entries(modelMetrics)) {
163
+ const usage = metrics.usage || {};
164
+ records.push({
165
+ raw_line: record.line,
166
+ span_id: event.id || null,
167
+ trace_id: session.id || null,
168
+ parent_span_id: event.parentId || null,
169
+ timestamp: event.timestamp || null,
170
+ surface: 'copilot-cli-session',
171
+ conversation_id: session.conversationId,
172
+ session_id: session.id,
173
+ requested_model: model,
174
+ resolved_model: model,
175
+ repo: session.repo,
176
+ branch: session.branch,
177
+ cwd: session.cwd,
178
+ commit_sha: session.commit,
179
+ labels: Array.from(session.labels).sort(),
180
+ input_tokens: Number(usage.inputTokens || 0),
181
+ output_tokens: Number(usage.outputTokens || 0),
182
+ cache_read_tokens: Number(usage.cacheReadTokens || 0),
183
+ cache_creation_tokens: Number(usage.cacheWriteTokens || 0),
184
+ reasoning_tokens: Number(usage.reasoningTokens || 0),
185
+ warnings: [],
186
+ });
187
+ }
188
+ }
189
+ return records;
190
+ }
191
+
105
192
  function normalizeHookEvent(payload, source, rawLine) {
106
193
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
107
194
  return {
@@ -122,5 +209,6 @@ module.exports = {
122
209
  flattenSpans,
123
210
  classifySpan,
124
211
  normalizePayload,
212
+ normalizeCopilotSessionEvents,
125
213
  normalizeHookEvent,
126
214
  };
package/src/paths.js CHANGED
@@ -28,6 +28,7 @@ function resolvePaths(options = {}) {
28
28
  const storeDir = path.join(home, 'store');
29
29
  const skillsDir = path.join(home, 'skills');
30
30
  const copilotHome = env.COPILOT_HOME || path.join(os.homedir(), '.copilot');
31
+ const copilotSessionStateDir = path.join(copilotHome, 'session-state');
31
32
 
32
33
  return {
33
34
  home,
@@ -40,6 +41,8 @@ function resolvePaths(options = {}) {
40
41
  hookEventsJsonl: path.join(hooksDir, 'copilot-hooks.jsonl'),
41
42
  usageDb: path.join(storeDir, 'copilot-metrics.sqlite'),
42
43
  configJson: path.join(home, 'config.json'),
44
+ copilotHome,
45
+ copilotSessionStateDir,
43
46
  localHookConfig: path.join(cwd, '.github', 'hooks', 'copilot-metrics.json'),
44
47
  globalHookConfig: path.join(copilotHome, 'settings.json'),
45
48
  };
package/src/setup.js CHANGED
@@ -59,6 +59,7 @@ function ensureDataDirs(paths) {
59
59
  copilotCli: {
60
60
  telemetry: paths.copilotCliOtelJsonl,
61
61
  hooks: paths.hookEventsJsonl,
62
+ sessions: paths.copilotSessionStateDir,
62
63
  },
63
64
  },
64
65
  labelExtractors: [],