@newrelic/preflight 1.15.6 → 1.16.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.
Files changed (70) hide show
  1. package/dist/config.d.ts +3 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +3 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/dashboard/dashboard-server.d.ts.map +1 -1
  6. package/dist/dashboard/dashboard-server.js +15 -0
  7. package/dist/dashboard/dashboard-server.js.map +1 -1
  8. package/dist/dashboard/routes/api-handler.d.ts +26 -18
  9. package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
  10. package/dist/dashboard/routes/api-handler.js +23 -15
  11. package/dist/dashboard/routes/api-handler.js.map +1 -1
  12. package/dist/data/copilot-pricing/README.md +42 -0
  13. package/dist/data/copilot-pricing/pricing.json +54 -0
  14. package/dist/data/copilot-sdk-extension/extension.mjs +123 -0
  15. package/dist/deploy/data-paths.d.ts +6 -4
  16. package/dist/deploy/data-paths.d.ts.map +1 -1
  17. package/dist/deploy/data-paths.js +5 -3
  18. package/dist/deploy/data-paths.js.map +1 -1
  19. package/dist/hooks/collector-script.d.ts +12 -3
  20. package/dist/hooks/collector-script.d.ts.map +1 -1
  21. package/dist/hooks/collector-script.js +70 -18
  22. package/dist/hooks/collector-script.js.map +1 -1
  23. package/dist/hooks/copilot-sdk-usage-mapper.d.ts +45 -0
  24. package/dist/hooks/copilot-sdk-usage-mapper.d.ts.map +1 -0
  25. package/dist/hooks/copilot-sdk-usage-mapper.js +73 -0
  26. package/dist/hooks/copilot-sdk-usage-mapper.js.map +1 -0
  27. package/dist/hooks/copilot-usage-watcher.d.ts +109 -0
  28. package/dist/hooks/copilot-usage-watcher.d.ts.map +1 -0
  29. package/dist/hooks/copilot-usage-watcher.js +436 -0
  30. package/dist/hooks/copilot-usage-watcher.js.map +1 -0
  31. package/dist/index.d.ts +11 -11
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +162 -70
  34. package/dist/index.js.map +1 -1
  35. package/dist/metrics/copilot-pricing-overlay.d.ts +42 -0
  36. package/dist/metrics/copilot-pricing-overlay.d.ts.map +1 -0
  37. package/dist/metrics/copilot-pricing-overlay.js +97 -0
  38. package/dist/metrics/copilot-pricing-overlay.js.map +1 -0
  39. package/dist/metrics/git-efficiency-tracker.d.ts +12 -1
  40. package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -1
  41. package/dist/metrics/git-efficiency-tracker.js +18 -3
  42. package/dist/metrics/git-efficiency-tracker.js.map +1 -1
  43. package/dist/metrics/local-session-aggregator.d.ts +164 -0
  44. package/dist/metrics/local-session-aggregator.d.ts.map +1 -0
  45. package/dist/metrics/local-session-aggregator.js +444 -0
  46. package/dist/metrics/local-session-aggregator.js.map +1 -0
  47. package/dist/platforms/copilot-adapter.d.ts +1 -1
  48. package/dist/platforms/copilot-adapter.d.ts.map +1 -1
  49. package/dist/platforms/copilot-adapter.js +64 -10
  50. package/dist/platforms/copilot-adapter.js.map +1 -1
  51. package/dist/platforms/copilot-sdk-adapter.d.ts +24 -0
  52. package/dist/platforms/copilot-sdk-adapter.d.ts.map +1 -0
  53. package/dist/platforms/copilot-sdk-adapter.js +173 -0
  54. package/dist/platforms/copilot-sdk-adapter.js.map +1 -0
  55. package/dist/platforms/index.d.ts +1 -0
  56. package/dist/platforms/index.d.ts.map +1 -1
  57. package/dist/platforms/index.js +1 -0
  58. package/dist/platforms/index.js.map +1 -1
  59. package/dist/platforms/platform-registry.d.ts.map +1 -1
  60. package/dist/platforms/platform-registry.js +13 -11
  61. package/dist/platforms/platform-registry.js.map +1 -1
  62. package/dist/storage/session-store.d.ts +9 -0
  63. package/dist/storage/session-store.d.ts.map +1 -1
  64. package/dist/storage/session-store.js +213 -20
  65. package/dist/storage/session-store.js.map +1 -1
  66. package/dist/web/assets/{index-BasTEtUs.js → index-Bwc4qaqj.js} +1 -1
  67. package/dist/web/assets/index-DwdSz9gz.css +2 -0
  68. package/dist/web/index.html +2 -2
  69. package/package.json +2 -2
  70. package/dist/web/assets/index-BI-2ELf2.css +0 -2
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Maps the Copilot agent-host's built-in tool names to Preflight's canonical
3
+ * (Claude-Code-shaped) vocabulary. Source: GitHub's own Copilot hooks
4
+ * reference, "Claude-format matchers (PascalCase PreToolUse)" section
5
+ * (docs.github.com/en/copilot/reference/copilot-cli-reference/cli-hooks-reference#pretooluse--pretooluse),
6
+ * which documents this exact runtime-tool → Claude-tool-name table.
7
+ *
8
+ * Under the PascalCase `PreToolUse`/`PostToolUse` hook config this adapter's
9
+ * setup instructions require (see getHookInstallInstructions() below), the
10
+ * CLI has ALREADY canonicalized `tool_name` to these Claude-shaped values
11
+ * before Preflight ever sees them (confirmed in the same reference: "Payloads
12
+ * for PascalCase PreToolUse report tool_name as the Claude tool name, for
13
+ * example Bash, not bash"). The canonical-name entries below are therefore
14
+ * the ones that actually get looked up in practice; the lowercase
15
+ * runtime-name entries are kept only as a defensive fallback in case some
16
+ * other Copilot SDK host ever delivers un-canonicalized names.
17
+ *
18
+ * Deliberately unmapped (no Claude equivalent per the same reference table,
19
+ * preserved as the original name downstream): the shell session-management
20
+ * variants (list_bash/read_bash/stop_bash/write_bash and their powershell
21
+ * equivalents — these manage a *background* shell session rather than
22
+ * running a command, unlike bash/powershell itself), list_agents,
23
+ * read_agent, write_agent, skill. This mirrors the VS Code CopilotAdapter's
24
+ * COPILOT_TOOL_MAP precedent of leaving introspection tools unmapped rather
25
+ * than guessing.
26
+ */
27
+ const COPILOT_SDK_TOOL_MAP = {
28
+ // Canonical Claude-shaped names, as actually delivered by PascalCase
29
+ // PreToolUse/PostToolUse (identity — see the module doc above).
30
+ Bash: 'Bash',
31
+ Read: 'Read',
32
+ Write: 'Write',
33
+ Edit: 'Edit',
34
+ Grep: 'Grep',
35
+ Glob: 'Glob',
36
+ Agent: 'Agent',
37
+ Task: 'Agent', // GitHub's docs note the literal 'Task' is also accepted.
38
+ WebFetch: 'WebFetch',
39
+ WebSearch: 'WebSearch',
40
+ AskUserQuestion: 'AskUserQuestion',
41
+ TodoWrite: 'TodoWrite',
42
+ // Raw runtime names — defensive fallback only; not expected to be looked
43
+ // up under the documented PascalCase config, which already canonicalizes
44
+ // tool_name before this adapter sees it.
45
+ bash: 'Bash',
46
+ powershell: 'Bash',
47
+ apply_patch: 'Edit',
48
+ create: 'Write',
49
+ edit: 'Edit',
50
+ str_replace_editor: 'Edit',
51
+ view: 'Read',
52
+ task: 'Agent',
53
+ glob: 'Glob',
54
+ grep: 'Grep',
55
+ rg: 'Grep',
56
+ web_fetch: 'WebFetch',
57
+ web_search: 'WebSearch',
58
+ ask_user: 'AskUserQuestion',
59
+ update_todo: 'TodoWrite',
60
+ };
61
+ function isCopilotSdkToolCallEvent(x) {
62
+ return typeof x === 'object' && x !== null;
63
+ }
64
+ /**
65
+ * Adapter for the GitHub Copilot SDK / agent-host runtime — distinct from the
66
+ * VS Code Copilot Chat adapter (`CopilotAdapter`): different session-id space
67
+ * (no `workspaceStorage`; sessions live in `~/.copilot/session-state/<id>/`),
68
+ * and different tool-name vocabulary (`bash`/`edit`/`grep` rather than VS
69
+ * Code's `run_in_terminal`/`replace_string_in_file`). The Copilot CLI is the
70
+ * confirmed host of this runtime; the same SDK extension mechanism plausibly
71
+ * hosts elsewhere (e.g. the desktop app), untested.
72
+ */
73
+ export class CopilotSdkAdapter {
74
+ platformName = 'copilot-sdk';
75
+ // The agent host's hooks fire on every built-in tool call via the same
76
+ // uniform hook envelope collector-script.ts already parses — but only
77
+ // when configured with the PascalCase event names PreToolUse/PostToolUse
78
+ // (see getHookInstallInstructions() below), which deliver the
79
+ // VS-Code-compatible snake_case payload (hook_event_name/tool_name/...)
80
+ // collector-script.ts's uniform branch expects. GitHub's native
81
+ // lowerCamelCase config (preToolUse) is NOT supported here: it emits an
82
+ // incompatible payload shape (sessionId/toolName, no hook_event_name field
83
+ // at all) that the collector's uniform branch can't parse. See
84
+ // docs/ADAPTERS.md's Copilot SDK section.
85
+ visibilityLevel = 'full-hooks';
86
+ // Repo-relative instruction files the host itself reads (Git root and cwd),
87
+ // per GitHub's own "Add custom instructions" guide for Copilot CLI
88
+ // (docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-custom-instructions#custom-instructions-locations),
89
+ // which also lists `CLAUDE.md` and `GEMINI.md` alongside `AGENTS.md` and
90
+ // `.github/copilot-instructions.md`. The same table lists a user-level
91
+ // `$HOME/.copilot/copilot-instructions.md` and a glob-based
92
+ // `.github/instructions/**/*.instructions.md` — neither fits here: every
93
+ // existing adapter's instructionFilePaths entry is a repo-relative exact
94
+ // filename matched against a tool call's filePath (see
95
+ // ClaudeMdTracker.matchesInstructionFile), which supports neither a
96
+ // per-user global path nor a glob pattern.
97
+ capabilities = {
98
+ instructionFilePaths: [
99
+ 'AGENTS.md',
100
+ 'CLAUDE.md',
101
+ 'GEMINI.md',
102
+ '.github/copilot-instructions.md',
103
+ ],
104
+ };
105
+ async initialize(_config) {
106
+ // Tool calls arrive via the Copilot agent host's hooks (collector
107
+ // script), parsed by collector-script.ts's uniform branch.
108
+ }
109
+ normalizeToolCall(raw) {
110
+ const event = isCopilotSdkToolCallEvent(raw) ? raw : {};
111
+ const platformToolName = event.tool ?? event.toolName ?? 'unknown';
112
+ const toolName = COPILOT_SDK_TOOL_MAP[platformToolName] ?? 'Unknown';
113
+ const filePath = event.filePath ?? event.path;
114
+ return {
115
+ toolName,
116
+ platformToolName,
117
+ platform: this.platformName,
118
+ timestamp: event.timestamp ?? Date.now(),
119
+ durationMs: event.durationMs ?? null,
120
+ success: event.success ?? true,
121
+ ...(event.error !== undefined && { error: event.error }),
122
+ ...(event.inputSizeBytes !== undefined && { inputSizeBytes: event.inputSizeBytes }),
123
+ ...(event.outputSizeBytes !== undefined && { outputSizeBytes: event.outputSizeBytes }),
124
+ ...(filePath !== undefined && { filePath }),
125
+ ...(event.command !== undefined && { command: event.command }),
126
+ ...(event.sessionId !== undefined && { sessionId: event.sessionId }),
127
+ };
128
+ }
129
+ mapToolName(platformToolName) {
130
+ return COPILOT_SDK_TOOL_MAP[platformToolName] ?? 'Unknown';
131
+ }
132
+ getSessionMetadata() {
133
+ return {
134
+ platform: this.platformName,
135
+ };
136
+ }
137
+ getHookInstallInstructions() {
138
+ return [
139
+ 'GitHub Copilot (SDK / agent-host, e.g. the Copilot CLI) Setup:',
140
+ '1. Create a hooks file to enable tool-call capture — user-level',
141
+ ' ~/.copilot/hooks/preflight.json (applies to all sessions) or',
142
+ ' workspace-level .github/hooks/preflight.json:',
143
+ ' {',
144
+ ' "version": 1,',
145
+ ' "hooks": {',
146
+ ' "PreToolUse": [{ "type": "command", "command": "preflight-collector pre-tool" }],',
147
+ ' "PostToolUse": [{ "type": "command", "command": "preflight-collector post-tool" }]',
148
+ ' }',
149
+ ' }',
150
+ '2. Ensure preflight-collector is on PATH (npm link, or npm install -g @newrelic/preflight)',
151
+ '3. Register the Preflight MCP server:',
152
+ ' copilot mcp add preflight \\',
153
+ ' --env MCP_CLIENT=copilot-sdk \\',
154
+ ' --env NEW_RELIC_LICENSE_KEY=<your-key> \\',
155
+ ' --env NEW_RELIC_ACCOUNT_ID=<your-account-id> \\',
156
+ ' -- npx preflight --stdio',
157
+ '4. (Optional, for token-exact cost) Copy the bundled Copilot SDK extension',
158
+ ' to pick up per-call token counts (the hooks above cover tool calls only):',
159
+ ' mkdir -p ~/.copilot/extensions/preflight',
160
+ ' cp <preflight-install-dir>/data/copilot-sdk-extension/extension.mjs \\',
161
+ ' ~/.copilot/extensions/preflight/extension.mjs',
162
+ '5. Restart the host (extensions load at startup) with --experimental',
163
+ ' (or run /experimental on) — extensions require the experimental',
164
+ ' feature flag. If already open, asking Copilot to reload extensions',
165
+ ' or running /clear may also pick up a newly-copied extension.',
166
+ ].join('\n');
167
+ }
168
+ isSupported() {
169
+ return (process.env.MCP_CLIENT === 'copilot-sdk' ||
170
+ process.env.NEW_RELIC_AI_PLATFORM === 'copilot-sdk');
171
+ }
172
+ }
173
+ //# sourceMappingURL=copilot-sdk-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilot-sdk-adapter.js","sourceRoot":"","sources":["../../src/platforms/copilot-sdk-adapter.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ export { ClaudeCodeAdapter } from './claude-code-adapter.js';
3
3
  export { CursorAdapter } from './cursor-adapter.js';
4
4
  export { WindsurfAdapter } from './windsurf-adapter.js';
5
5
  export { CopilotAdapter } from './copilot-adapter.js';
6
+ export { CopilotSdkAdapter } from './copilot-sdk-adapter.js';
6
7
  export type { CopilotToolCallEvent, CopilotUsageRecord } from './copilot-adapter.js';
7
8
  export { parseCopilotUsageResponse } from './copilot-adapter.js';
8
9
  export { ZedAdapter } from './zed-adapter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platforms/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC1F,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platforms/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC1F,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -2,6 +2,7 @@ export { ClaudeCodeAdapter } from './claude-code-adapter.js';
2
2
  export { CursorAdapter } from './cursor-adapter.js';
3
3
  export { WindsurfAdapter } from './windsurf-adapter.js';
4
4
  export { CopilotAdapter } from './copilot-adapter.js';
5
+ export { CopilotSdkAdapter } from './copilot-sdk-adapter.js';
5
6
  export { parseCopilotUsageResponse } from './copilot-adapter.js';
6
7
  export { ZedAdapter } from './zed-adapter.js';
7
8
  export { ContinueAdapter } from './continue-adapter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/platforms/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAM1F,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/platforms/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAM1F,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"platform-registry.d.ts","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAqB3E,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,MAAM,CAAgC;IAE9C,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAKxC,MAAM,IAAI,eAAe,GAAG,IAAI;IAahC,SAAS,IAAI,eAAe;IAY5B,aAAa,IAAI,SAAS,eAAe,EAAE;CAG5C;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAoBxD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAKvF"}
1
+ {"version":3,"file":"platform-registry.d.ts","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAM3E,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,MAAM,CAAgC;IAE9C,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAKxC,MAAM,IAAI,eAAe,GAAG,IAAI;IAahC,SAAS,IAAI,eAAe;IAY5B,aAAa,IAAI,SAAS,eAAe,EAAE;CAG5C;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAqBxD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAKvF"}
@@ -1,21 +1,22 @@
1
1
  import { createLogger } from '../shared/index.js';
2
+ import { AmazonQAdapter } from './amazon-q-adapter.js';
3
+ import { AntigravityAdapter } from './antigravity-adapter.js';
2
4
  import { ClaudeCodeAdapter } from './claude-code-adapter.js';
3
- import { CursorAdapter } from './cursor-adapter.js';
4
- import { WindsurfAdapter } from './windsurf-adapter.js';
5
- import { CopilotAdapter } from './copilot-adapter.js';
6
- import { ZedAdapter } from './zed-adapter.js';
5
+ import { ClineAdapter } from './cline-adapter.js';
6
+ import { CodexAdapter } from './codex-adapter.js';
7
7
  import { ContinueAdapter } from './continue-adapter.js';
8
- import { AmazonQAdapter } from './amazon-q-adapter.js';
9
- import { KiroAdapter } from './kiro-adapter.js';
8
+ import { CopilotAdapter } from './copilot-adapter.js';
9
+ import { CopilotSdkAdapter } from './copilot-sdk-adapter.js';
10
+ import { CursorAdapter } from './cursor-adapter.js';
10
11
  import { DroidAdapter } from './droid-adapter.js';
11
12
  import { GeminiCliAdapter } from './gemini-cli-adapter.js';
12
- import { ClineAdapter } from './cline-adapter.js';
13
- import { CodexAdapter } from './codex-adapter.js';
14
- import { OpencodeAdapter } from './opencode-adapter.js';
13
+ import { GenericMcpAdapter } from './generic-mcp-adapter.js';
15
14
  import { KiloCodeAdapter } from './kilo-code-adapter.js';
15
+ import { KiroAdapter } from './kiro-adapter.js';
16
+ import { OpencodeAdapter } from './opencode-adapter.js';
16
17
  import { PiAdapter } from './pi-adapter.js';
17
- import { AntigravityAdapter } from './antigravity-adapter.js';
18
- import { GenericMcpAdapter } from './generic-mcp-adapter.js';
18
+ import { WindsurfAdapter } from './windsurf-adapter.js';
19
+ import { ZedAdapter } from './zed-adapter.js';
19
20
  const logger = createLogger('platform-registry');
20
21
  export class PlatformRegistry {
21
22
  adapters = [];
@@ -54,6 +55,7 @@ export function createDefaultRegistry() {
54
55
  registry.register(new CursorAdapter());
55
56
  registry.register(new WindsurfAdapter());
56
57
  registry.register(new CopilotAdapter());
58
+ registry.register(new CopilotSdkAdapter());
57
59
  registry.register(new ZedAdapter());
58
60
  registry.register(new ContinueAdapter());
59
61
  registry.register(new AmazonQAdapter());
@@ -1 +1 @@
1
- {"version":3,"file":"platform-registry.js","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-registry.js","sourceRoot":"","sources":["../../src/platforms/platform-registry.ts"],"names":[],"mappings":"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"}
@@ -153,6 +153,13 @@ export interface ListSessionsOptions {
153
153
  since?: Date;
154
154
  developer?: string;
155
155
  }
156
+ /**
157
+ * Combine two views of the same session so neither writer can lose the other's
158
+ * data. Counters take the max rather than the sum: the two processes observe
159
+ * overlapping (not disjoint) slices of one session, so summing would inflate
160
+ * and taking the incoming value alone would regress.
161
+ */
162
+ export declare function mergeSummaries(existing: FullSessionSummary, incoming: FullSessionSummary): FullSessionSummary;
156
163
  export declare class SessionStore {
157
164
  private readonly sessionsDir;
158
165
  constructor(options: {
@@ -161,6 +168,8 @@ export declare class SessionStore {
161
168
  getSessionsDir(): string;
162
169
  saveSession(summary: FullSessionSummary): void;
163
170
  loadSession(sessionId: string): FullSessionSummary | null;
171
+ /** Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename. */
172
+ private loadSessionWithPath;
164
173
  listSessions(options?: ListSessionsOptions): SessionFileInfo[];
165
174
  loadAllSessions(options?: ListSessionsOptions): FullSessionSummary[];
166
175
  loadTodaySessions(): FullSessionSummary[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/storage/session-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/storage/session-store.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  * `buildSessionSummary()` pulls getMetrics() from each tracker and aggregates
12
12
  * task-level data into a single FullSessionSummary.
13
13
  */
14
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
14
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync, } from 'node:fs';
15
15
  import { join, resolve, sep } from 'node:path';
16
16
  import { createLogger } from '../shared/index.js';
17
17
  import { redactSensitive } from '../config.js';
@@ -38,6 +38,141 @@ export function toPersistedAntiPatterns(patterns) {
38
38
  // ---------------------------------------------------------------------------
39
39
  // SessionStore
40
40
  // ---------------------------------------------------------------------------
41
+ /**
42
+ * Combine two views of the same session so neither writer can lose the other's
43
+ * data. Counters take the max rather than the sum: the two processes observe
44
+ * overlapping (not disjoint) slices of one session, so summing would inflate
45
+ * and taking the incoming value alone would regress.
46
+ */
47
+ export function mergeSummaries(existing, incoming) {
48
+ const maxNum = (a, b) => Math.max(typeof a === 'number' ? a : 0, typeof b === 'number' ? b : 0);
49
+ const maxNullable = (a, b) => a === null && b === null ? null : Math.max(a ?? 0, b ?? 0);
50
+ const mergeCounts = (a = {}, b = {}) => {
51
+ const out = { ...a };
52
+ for (const [k, v] of Object.entries(b))
53
+ out[k] = Math.max(out[k] ?? 0, v);
54
+ return out;
55
+ };
56
+ const union = (a = [], b = []) => [...new Set([...a, ...b])];
57
+ // efficiencyScore/efficiencyScoreSampleCount/efficiencyScoreComponents must
58
+ // always come from the SAME side, atomically — EfficiencyScorer.
59
+ // seedFromPersisted() treats sampleCount as a weight multiplier
60
+ // (score * sampleCount), not just a display field, so merging the three
61
+ // independently (e.g. taking the newer score but the max sampleCount) can
62
+ // attach a thin/unrelated sample count to a completely different score,
63
+ // radically over- or under-weighting it on the next re-seed. When only one
64
+ // side has a real score, that side wins outright (this is what makes an
65
+ // unseeded write never erase a real prior score). When both do, the side
66
+ // with the larger sampleCount wins — the more statistically meaningful one.
67
+ const mergeEfficiencyScoreTriple = (existing, incoming) => {
68
+ const existingHasScore = existing.efficiencyScore !== null;
69
+ const incomingHasScore = incoming.efficiencyScore !== null;
70
+ const winner = existingHasScore && incomingHasScore
71
+ ? (incoming.efficiencyScoreSampleCount ?? 0) >= (existing.efficiencyScoreSampleCount ?? 0)
72
+ ? incoming
73
+ : existing
74
+ : incomingHasScore
75
+ ? incoming
76
+ : existing;
77
+ return {
78
+ efficiencyScore: winner.efficiencyScore,
79
+ efficiencyScoreSampleCount: winner.efficiencyScoreSampleCount ?? 0,
80
+ efficiencyScoreComponents: winner.efficiencyScoreComponents ?? null,
81
+ };
82
+ };
83
+ const mergeCostByWorkflowRunId = (a = {}, b = {}) => {
84
+ const out = { ...a };
85
+ for (const [runId, dayCosts] of Object.entries(b)) {
86
+ out[runId] = mergeCounts(out[runId], dayCosts);
87
+ }
88
+ return out;
89
+ };
90
+ const modelBreakdown = { ...existing.modelBreakdown };
91
+ for (const [model, entry] of Object.entries(incoming.modelBreakdown ?? {})) {
92
+ const prev = modelBreakdown[model];
93
+ modelBreakdown[model] = prev
94
+ ? {
95
+ requestCount: Math.max(prev.requestCount, entry.requestCount),
96
+ totalInputTokens: Math.max(prev.totalInputTokens, entry.totalInputTokens),
97
+ totalOutputTokens: Math.max(prev.totalOutputTokens, entry.totalOutputTokens),
98
+ totalCostUsd: Math.max(prev.totalCostUsd, entry.totalCostUsd),
99
+ }
100
+ : entry;
101
+ }
102
+ const qa = existing.qualityProxy ?? ZERO_QUALITY_PROXY_COUNTS;
103
+ const qb = incoming.qualityProxy ?? ZERO_QUALITY_PROXY_COUNTS;
104
+ const qualityProxy = {
105
+ totalSignals: maxNum(qa.totalSignals, qb.totalSignals),
106
+ diffApplyCleanCount: maxNum(qa.diffApplyCleanCount, qb.diffApplyCleanCount),
107
+ diffFailCount: maxNum(qa.diffFailCount, qb.diffFailCount),
108
+ testPassCount: maxNum(qa.testPassCount, qb.testPassCount),
109
+ testFailCount: maxNum(qa.testFailCount, qb.testFailCount),
110
+ backtrackCount: maxNum(qa.backtrackCount, qb.backtrackCount),
111
+ selfCorrectionCount: maxNum(qa.selfCorrectionCount, qb.selfCorrectionCount),
112
+ };
113
+ // Keep whichever tool-selection score saw more of the session; it is scored
114
+ // from an ordered record list that cannot be meaningfully merged field-wise.
115
+ const toolSelectionMetrics = (incoming.toolSelectionMetrics?.totalCalls ?? 0) >=
116
+ (existing.toolSelectionMetrics?.totalCalls ?? 0)
117
+ ? (incoming.toolSelectionMetrics ?? existing.toolSelectionMetrics)
118
+ : existing.toolSelectionMetrics;
119
+ const timeline = (incoming.timeline?.length ?? 0) >= (existing.timeline?.length ?? 0)
120
+ ? incoming.timeline
121
+ : existing.timeline;
122
+ const startTime = Math.min(existing.startTime || incoming.startTime, incoming.startTime || existing.startTime);
123
+ const endTime = maxNum(existing.endTime, incoming.endTime);
124
+ return {
125
+ ...existing,
126
+ ...incoming,
127
+ startTime,
128
+ endTime,
129
+ durationMs: Math.max(0, endTime - startTime),
130
+ toolCallCount: maxNum(existing.toolCallCount, incoming.toolCallCount),
131
+ sessionName: incoming.sessionName ?? existing.sessionName,
132
+ repoName: incoming.repoName ?? existing.repoName,
133
+ model: incoming.model ?? existing.model,
134
+ platform: incoming.platform ?? existing.platform,
135
+ instructionPromptHash: incoming.instructionPromptHash ?? existing.instructionPromptHash,
136
+ toolBreakdown: mergeCounts(existing.toolBreakdown, incoming.toolBreakdown),
137
+ filesRead: union(existing.filesRead, incoming.filesRead),
138
+ filesModified: union(existing.filesModified, incoming.filesModified),
139
+ linesAdded: maxNum(existing.linesAdded, incoming.linesAdded),
140
+ linesRemoved: maxNum(existing.linesRemoved, incoming.linesRemoved),
141
+ bashCommandCount: maxNum(existing.bashCommandCount, incoming.bashCommandCount),
142
+ testRunCount: maxNum(existing.testRunCount, incoming.testRunCount),
143
+ testPassCount: maxNum(existing.testPassCount, incoming.testPassCount),
144
+ buildRunCount: maxNum(existing.buildRunCount, incoming.buildRunCount),
145
+ buildPassCount: maxNum(existing.buildPassCount, incoming.buildPassCount),
146
+ estimatedCostUsd: maxNullable(existing.estimatedCostUsd, incoming.estimatedCostUsd),
147
+ subagentCostUsd: maxNum(existing.subagentCostUsd, incoming.subagentCostUsd),
148
+ tokensInput: maxNum(existing.tokensInput, incoming.tokensInput),
149
+ tokensOutput: maxNum(existing.tokensOutput, incoming.tokensOutput),
150
+ tokensThinking: maxNum(existing.tokensThinking, incoming.tokensThinking),
151
+ tokensCacheRead: maxNum(existing.tokensCacheRead, incoming.tokensCacheRead),
152
+ tokensCacheCreation: maxNum(existing.tokensCacheCreation, incoming.tokensCacheCreation),
153
+ cacheSavingsUsd: maxNum(existing.cacheSavingsUsd, incoming.cacheSavingsUsd),
154
+ ...mergeEfficiencyScoreTriple(existing, incoming),
155
+ costByWorkflowRunId: mergeCostByWorkflowRunId(existing.costByWorkflowRunId, incoming.costByWorkflowRunId),
156
+ taskCount: maxNum(existing.taskCount, incoming.taskCount),
157
+ taskSuccessRate: incoming.taskSuccessRate ?? existing.taskSuccessRate,
158
+ toolSuccessRate: incoming.toolSuccessRate ?? existing.toolSuccessRate,
159
+ contextCompressions: maxNum(existing.contextCompressions, incoming.contextCompressions),
160
+ agentSpawns: maxNum(existing.agentSpawns, incoming.agentSpawns),
161
+ userMessages: maxNum(existing.userMessages, incoming.userMessages),
162
+ assistantMessages: maxNum(existing.assistantMessages, incoming.assistantMessages),
163
+ userCorrections: maxNum(existing.userCorrections, incoming.userCorrections),
164
+ antiPatterns: (incoming.antiPatterns?.length ?? 0) >= (existing.antiPatterns?.length ?? 0)
165
+ ? incoming.antiPatterns
166
+ : existing.antiPatterns,
167
+ outcome: incoming.outcome === 'completed' || existing.outcome !== 'completed'
168
+ ? incoming.outcome
169
+ : existing.outcome,
170
+ ...(timeline ? { timeline } : {}),
171
+ toolSelectionMetrics,
172
+ modelBreakdown,
173
+ qualityProxy,
174
+ };
175
+ }
41
176
  export class SessionStore {
42
177
  sessionsDir;
43
178
  constructor(options) {
@@ -54,41 +189,98 @@ export class SessionStore {
54
189
  if (!existsSync(this.sessionsDir)) {
55
190
  mkdirSync(this.sessionsDir, { recursive: true, mode: 0o700 });
56
191
  }
57
- const startDate = new Date(summary.startTime);
192
+ // Look up any existing file for this sessionId BEFORE deciding the
193
+ // filename — by sessionId alone, not by today's date-prefixed path. A
194
+ // session can already have a file under an EARLIER date than this
195
+ // write's own startTime computes (e.g. a `--local`/synthetic-owner
196
+ // process rolling up a session it only started observing after a date
197
+ // boundary — see LocalSessionAggregator). Deciding the filename from
198
+ // `summary.startTime` alone before merging, then merging in whatever
199
+ // file happens to share the sessionId regardless of date, wrote the
200
+ // merged (inflated, since counters take the max) result to a NEW file
201
+ // while leaving the OLD file on disk untouched — the session then
202
+ // appeared twice in history with overlapping, double-counted totals.
203
+ const existingWithPath = this.loadSessionWithPath(summary.sessionId);
204
+ // A second process saving under the same sessionId (e.g. two MCP
205
+ // servers both resumed/forked against one real session ID) would
206
+ // otherwise silently overwrite the first save with no error.
207
+ //
208
+ // Guard the destructive case: a short-lived process that adopts an
209
+ // existing session ID (resolved from the session-by-cwd breadcrumb) but
210
+ // never observed any hook activity would replace a fully recorded session
211
+ // with an all-zero summary, erasing tool calls, tokens and cost from the
212
+ // dashboard on the next refresh. Never let an empty summary clobber a
213
+ // non-empty one; other collisions keep the previous last-write-wins
214
+ // behaviour with a warning.
215
+ let toWrite = summary;
216
+ if (existingWithPath) {
217
+ const { summary: existing } = existingWithPath;
218
+ const existingCalls = existing.toolCallCount ?? 0;
219
+ if (existingCalls > 0 && (summary.toolCallCount ?? 0) === 0) {
220
+ logger.warn('Refusing to overwrite recorded session with an empty summary', {
221
+ sessionId: summary.sessionId,
222
+ });
223
+ return;
224
+ }
225
+ // Two processes legitimately share one session id: the CLI/editor
226
+ // spawns an MCP engine that owns the session natively, while a
227
+ // `--local` dashboard adopts the same id from the session-by-cwd
228
+ // breadcrumb. Each sees only part of the picture — the engine has the
229
+ // token/model events, the dashboard has the aggregated hook activity —
230
+ // so plain last-write-wins let whichever wrote last erase the other's
231
+ // data, which is how a fully recorded session could come back with
232
+ // modelBreakdown {} and a lower toolCallCount. Merge non-destructively
233
+ // instead: counters take the max (they only ever grow, so max never
234
+ // regresses and never double-counts overlapping views).
235
+ toWrite = mergeSummaries(existing, summary);
236
+ }
237
+ // The filename is derived from the merged (earliest) startTime, not the
238
+ // incoming write's own — mergeSummaries() takes Math.min() of the two,
239
+ // so this is normally the existing file's own date; recomputing here
240
+ // keeps the filename consistent with what's actually written.
241
+ const startDate = new Date(toWrite.startTime);
58
242
  if (!isFinite(startDate.getTime())) {
59
- throw new Error(`Invalid startTime for session ${summary.sessionId}: ${summary.startTime}`);
243
+ throw new Error(`Invalid startTime for session ${toWrite.sessionId}: ${toWrite.startTime}`);
60
244
  }
61
245
  const date = startDate.toISOString().slice(0, 10);
62
- const filename = `${date}_${summary.sessionId}.json`;
246
+ const filename = `${date}_${toWrite.sessionId}.json`;
63
247
  const filepath = resolve(this.sessionsDir, filename);
64
248
  if (!filepath.startsWith(this.sessionsDir + sep)) {
65
249
  throw new Error(`Session path escaped storage directory: ${filepath}`);
66
250
  }
67
- // A second process saving under the same sessionId+date (e.g. two MCP
68
- // servers both resumed/forked against one real session ID) would
69
- // otherwise silently overwrite the first save with no error. This
70
- // warning makes that cross-process collision visible instead of a
71
- // silent last-write-wins; the overwrite itself is unchanged.
72
- if (existsSync(filepath)) {
73
- logger.warn('Overwriting existing session file — possible cross-process sessionId collision', {
74
- sessionId: summary.sessionId,
75
- filename,
76
- });
77
- }
78
251
  try {
79
- writeFileSync(filepath, JSON.stringify(summary, null, 2) + '\n', { mode: 0o600 });
80
- logger.debug('Session saved', { sessionId: summary.sessionId, filename });
252
+ writeFileSync(filepath, JSON.stringify(toWrite, null, 2) + '\n', { mode: 0o600 });
253
+ // The merge landed on a different filename than where the existing
254
+ // record lived (a date-rollover case) — remove the now-superseded
255
+ // file so the session isn't left duplicated across two files.
256
+ if (existingWithPath && existingWithPath.filepath !== filepath) {
257
+ try {
258
+ unlinkSync(existingWithPath.filepath);
259
+ }
260
+ catch (err) {
261
+ logger.warn('Failed to remove superseded session file after merge', {
262
+ sessionId: toWrite.sessionId,
263
+ stalePath: existingWithPath.filepath,
264
+ error: err instanceof Error ? err.message : String(err),
265
+ });
266
+ }
267
+ }
268
+ logger.debug('Session saved', { sessionId: toWrite.sessionId, filename });
81
269
  }
82
270
  catch (err) {
83
271
  const message = err instanceof Error ? err.message : String(err);
84
272
  logger.warn('Failed to save session file', {
85
- sessionId: summary.sessionId,
273
+ sessionId: toWrite.sessionId,
86
274
  filename,
87
275
  error: message,
88
276
  });
89
277
  }
90
278
  }
91
279
  loadSession(sessionId) {
280
+ return this.loadSessionWithPath(sessionId)?.summary ?? null;
281
+ }
282
+ /** Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename. */
283
+ loadSessionWithPath(sessionId) {
92
284
  if (!existsSync(this.sessionsDir))
93
285
  return null;
94
286
  for (const file of readdirSync(this.sessionsDir)) {
@@ -96,14 +288,15 @@ export class SessionStore {
96
288
  continue;
97
289
  if (parseSessionFilename(file)?.sessionId !== sessionId)
98
290
  continue;
291
+ const filepath = join(this.sessionsDir, file);
99
292
  try {
100
- const raw = readFileSync(join(this.sessionsDir, file), 'utf-8');
293
+ const raw = readFileSync(filepath, 'utf-8');
101
294
  const session = deserializeSession(raw);
102
295
  if (session === null) {
103
296
  logger.warn('Failed to deserialize session file', { file });
104
297
  return null;
105
298
  }
106
- return session;
299
+ return { summary: session, filepath };
107
300
  }
108
301
  catch {
109
302
  logger.warn('Failed to read session file', { file });