@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.
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/dashboard-server.d.ts.map +1 -1
- package/dist/dashboard/dashboard-server.js +15 -0
- package/dist/dashboard/dashboard-server.js.map +1 -1
- package/dist/dashboard/routes/api-handler.d.ts +26 -18
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
- package/dist/dashboard/routes/api-handler.js +23 -15
- package/dist/dashboard/routes/api-handler.js.map +1 -1
- package/dist/data/copilot-pricing/README.md +42 -0
- package/dist/data/copilot-pricing/pricing.json +54 -0
- package/dist/data/copilot-sdk-extension/extension.mjs +123 -0
- package/dist/deploy/data-paths.d.ts +6 -4
- package/dist/deploy/data-paths.d.ts.map +1 -1
- package/dist/deploy/data-paths.js +5 -3
- package/dist/deploy/data-paths.js.map +1 -1
- package/dist/hooks/collector-script.d.ts +12 -3
- package/dist/hooks/collector-script.d.ts.map +1 -1
- package/dist/hooks/collector-script.js +70 -18
- package/dist/hooks/collector-script.js.map +1 -1
- package/dist/hooks/copilot-sdk-usage-mapper.d.ts +45 -0
- package/dist/hooks/copilot-sdk-usage-mapper.d.ts.map +1 -0
- package/dist/hooks/copilot-sdk-usage-mapper.js +73 -0
- package/dist/hooks/copilot-sdk-usage-mapper.js.map +1 -0
- package/dist/hooks/copilot-usage-watcher.d.ts +109 -0
- package/dist/hooks/copilot-usage-watcher.d.ts.map +1 -0
- package/dist/hooks/copilot-usage-watcher.js +436 -0
- package/dist/hooks/copilot-usage-watcher.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +162 -70
- package/dist/index.js.map +1 -1
- package/dist/metrics/copilot-pricing-overlay.d.ts +42 -0
- package/dist/metrics/copilot-pricing-overlay.d.ts.map +1 -0
- package/dist/metrics/copilot-pricing-overlay.js +97 -0
- package/dist/metrics/copilot-pricing-overlay.js.map +1 -0
- package/dist/metrics/git-efficiency-tracker.d.ts +12 -1
- package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -1
- package/dist/metrics/git-efficiency-tracker.js +18 -3
- package/dist/metrics/git-efficiency-tracker.js.map +1 -1
- package/dist/metrics/local-session-aggregator.d.ts +164 -0
- package/dist/metrics/local-session-aggregator.d.ts.map +1 -0
- package/dist/metrics/local-session-aggregator.js +444 -0
- package/dist/metrics/local-session-aggregator.js.map +1 -0
- package/dist/platforms/copilot-adapter.d.ts +1 -1
- package/dist/platforms/copilot-adapter.d.ts.map +1 -1
- package/dist/platforms/copilot-adapter.js +64 -10
- package/dist/platforms/copilot-adapter.js.map +1 -1
- package/dist/platforms/copilot-sdk-adapter.d.ts +24 -0
- package/dist/platforms/copilot-sdk-adapter.d.ts.map +1 -0
- package/dist/platforms/copilot-sdk-adapter.js +173 -0
- package/dist/platforms/copilot-sdk-adapter.js.map +1 -0
- package/dist/platforms/index.d.ts +1 -0
- package/dist/platforms/index.d.ts.map +1 -1
- package/dist/platforms/index.js +1 -0
- package/dist/platforms/index.js.map +1 -1
- package/dist/platforms/platform-registry.d.ts.map +1 -1
- package/dist/platforms/platform-registry.js +13 -11
- package/dist/platforms/platform-registry.js.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/session-store.js +213 -20
- package/dist/storage/session-store.js.map +1 -1
- package/dist/web/assets/{index-BasTEtUs.js → index-Bwc4qaqj.js} +1 -1
- package/dist/web/assets/index-DwdSz9gz.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- 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":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,oBAAoB,GAA2B;IACnD,qEAAqE;IACrE,gEAAgE;IAChE,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,OAAO,EAAE,0DAA0D;IACzE,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,eAAe,EAAE,iBAAiB;IAClC,SAAS,EAAE,WAAW;IACtB,yEAAyE;IACzE,yEAAyE;IACzE,yCAAyC;IACzC,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,MAAM;IAClB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,MAAM;IACZ,kBAAkB,EAAE,MAAM;IAC1B,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,EAAE,EAAE,MAAM;IACV,SAAS,EAAE,UAAU;IACrB,UAAU,EAAE,WAAW;IACvB,QAAQ,EAAE,iBAAiB;IAC3B,WAAW,EAAE,WAAW;CACzB,CAAC;AAiBF,SAAS,yBAAyB,CAAC,CAAU;IAC3C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAiB;IACnB,YAAY,GAAG,aAAa,CAAC;IACtC,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,8DAA8D;IAC9D,wEAAwE;IACxE,gEAAgE;IAChE,wEAAwE;IACxE,2EAA2E;IAC3E,+DAA+D;IAC/D,0CAA0C;IACjC,eAAe,GAAG,YAAqB,CAAC;IACjD,4EAA4E;IAC5E,mEAAmE;IACnE,4HAA4H;IAC5H,yEAAyE;IACzE,uEAAuE;IACvE,4DAA4D;IAC5D,yEAAyE;IACzE,yEAAyE;IACzE,uDAAuD;IACvD,oEAAoE;IACpE,2CAA2C;IAClC,YAAY,GAAG;QACtB,oBAAoB,EAAE;YACpB,WAAW;YACX,WAAW;YACX,WAAW;YACX,iCAAiC;SACzB;KACX,CAAC;IAEF,KAAK,CAAC,UAAU,CAAC,OAAuB;QACtC,kEAAkE;QAClE,2DAA2D;IAC7D,CAAC;IAED,iBAAiB,CAAC,GAAY;QAC5B,MAAM,KAAK,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,gBAAgB,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,SAAS,CAAC;QACnE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,gBAAgB,CAAC,IAAI,SAAS,CAAC;QACrE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC;QAE9C,OAAO;YACL,QAAQ;YACR,gBAAgB;YAChB,QAAQ,EAAE,IAAI,CAAC,YAAY;YAC3B,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;YACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;YACpC,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI;YAC9B,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YACxD,GAAG,CAAC,KAAK,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC;YACnF,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,SAAS,IAAI,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,CAAC;YACtF,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC3C,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAC9D,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;SACrE,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,gBAAwB;QAClC,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,IAAI,SAAS,CAAC;IAC7D,CAAC;IAED,kBAAkB;QAChB,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,YAAY;SAC5B,CAAC;IACJ,CAAC;IAED,0BAA0B;QACxB,OAAO;YACL,gEAAgE;YAChE,iEAAiE;YACjE,iEAAiE;YACjE,kDAAkD;YAClD,MAAM;YACN,oBAAoB;YACpB,iBAAiB;YACjB,0FAA0F;YAC1F,2FAA2F;YAC3F,QAAQ;YACR,MAAM;YACN,4FAA4F;YAC5F,uCAAuC;YACvC,iCAAiC;YACjC,sCAAsC;YACtC,gDAAgD;YAChD,sDAAsD;YACtD,+BAA+B;YAC/B,4EAA4E;YAC5E,8EAA8E;YAC9E,6CAA6C;YAC7C,2EAA2E;YAC3E,oDAAoD;YACpD,sEAAsE;YACtE,oEAAoE;YACpE,uEAAuE;YACvE,iEAAiE;SAClE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED,WAAW;QACT,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,aAAa;YACxC,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,aAAa,CACpD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -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"}
|
package/dist/platforms/index.js
CHANGED
|
@@ -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;
|
|
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":"
|
|
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 {
|
|
4
|
-
import {
|
|
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 {
|
|
9
|
-
import {
|
|
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 {
|
|
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 {
|
|
18
|
-
import {
|
|
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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
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":"AAAA;;;;;;;;;;;;GAYG;
|
|
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
|
-
|
|
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 ${
|
|
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}_${
|
|
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(
|
|
80
|
-
|
|
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:
|
|
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(
|
|
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 });
|