agent-profiler 0.1.0 → 1.0.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/README.md +66 -0
- package/dist/cli.js +5 -4
- package/dist/commands/init.js +4 -0
- package/dist/commands/status.js +36 -9
- package/dist/core/db.js +164 -28
- package/dist/core/gitWorkspace.js +46 -5
- package/dist/core/packageMeta.js +20 -0
- package/dist/core/schema.sql +8 -0
- package/package.json +57 -3
- package/agent-profiler-0.1.0.tgz +0 -0
- package/docs/agent-profiler-mvp-handoff.md +0 -980
- package/google-home.png +0 -0
- package/src/adapters/codex.ts +0 -131
- package/src/adapters/cursor.ts +0 -115
- package/src/cli.ts +0 -109
- package/src/commands/auditContext.ts +0 -62
- package/src/commands/hook.ts +0 -104
- package/src/commands/init.ts +0 -324
- package/src/commands/last.ts +0 -326
- package/src/commands/status.ts +0 -345
- package/src/core/contextAudit.ts +0 -102
- package/src/core/db.ts +0 -491
- package/src/core/eventMetadata.ts +0 -184
- package/src/core/gitWorkspace.ts +0 -92
- package/src/core/normalize.ts +0 -29
- package/src/core/profile.ts +0 -35
- package/src/core/schema.sql +0 -56
- package/src/core/tokens.ts +0 -4
- package/src/types/better-sqlite3.d.ts +0 -26
- package/tsconfig.json +0 -15
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# agent-profiler
|
|
2
|
+
|
|
3
|
+
Local-first profiling for AI coding agents.
|
|
4
|
+
|
|
5
|
+
`agent-profiler` captures local Cursor and Codex hook events into SQLite so you can inspect recent sessions, setup state, and always-on context overhead without sending data to a remote service.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js 22 or newer
|
|
10
|
+
- macOS, Linux, or another environment supported by `better-sqlite3`
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
For one-off commands, use `npx`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx agent-profiler --help
|
|
18
|
+
npx agent-profiler last
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For persistent hook installation, install the package so `agent-profiler` is available on `PATH`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g agent-profiler
|
|
25
|
+
agent-profiler --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
Initialize hooks for Cursor:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agent-profiler init cursor --mode prod
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Initialize hooks for Codex:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
agent-profiler init codex --mode prod
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Inspect the resulting setup and the latest captured session:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
agent-profiler status
|
|
46
|
+
agent-profiler last
|
|
47
|
+
agent-profiler audit context
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## `npx` vs `--mode prod`
|
|
51
|
+
|
|
52
|
+
- `npx agent-profiler ...` is great for one-off inspection commands.
|
|
53
|
+
- `agent-profiler init <source> --mode prod` writes persistent hook commands that expect `agent-profiler` to be installed on `PATH`.
|
|
54
|
+
- `--mode dev` is meant for local development in this repository and writes absolute `node <repo>/dist/cli.js ...` hook commands instead.
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
- `agent-profiler init <cursor|codex>`: install hook wiring for a supported source
|
|
59
|
+
- `agent-profiler hook <source> <eventName>`: ingest one hook payload from stdin
|
|
60
|
+
- `agent-profiler status`: inspect local setup and ingest state
|
|
61
|
+
- `agent-profiler last`: summarize the most recent observed session
|
|
62
|
+
- `agent-profiler audit context`: estimate always-on context token footprint
|
|
63
|
+
|
|
64
|
+
## Releases
|
|
65
|
+
|
|
66
|
+
Releases are automated with semantic-release. Pull requests run CI plus canary publishing, and pushes to `main` publish to npm and create a GitHub release.
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { getAuditContextReport, runAuditContext } from "./commands/auditContext.js";
|
|
3
|
+
import { getAuditContextReport, runAuditContext, } from "./commands/auditContext.js";
|
|
4
4
|
import { runHook } from "./commands/hook.js";
|
|
5
5
|
import { runInit } from "./commands/init.js";
|
|
6
6
|
import { getLastReport, runLast } from "./commands/last.js";
|
|
7
7
|
import { getStatusReport, runStatus } from "./commands/status.js";
|
|
8
|
+
import { getPackageVersion } from "./core/packageMeta.js";
|
|
8
9
|
const program = new Command();
|
|
9
10
|
program
|
|
10
11
|
.name("agent-profiler")
|
|
11
12
|
.description("Local-first profiling for AI coding agents.")
|
|
12
|
-
.version(
|
|
13
|
+
.version(getPackageVersion());
|
|
13
14
|
program
|
|
14
15
|
.command("init")
|
|
15
16
|
.description("Initialize Agent Profiler for a supported source.")
|
|
16
17
|
.argument("<source>", "supported: cursor | codex")
|
|
17
|
-
.option("--mode <mode>", "init mode: dev or prod", "dev")
|
|
18
|
+
.option("--mode <mode>", "init mode: dev or prod (prod requires `agent-profiler` on PATH)", "dev")
|
|
18
19
|
.action((source, options) => {
|
|
19
20
|
const allowed = ["cursor", "codex"];
|
|
20
21
|
if (!allowed.includes(source)) {
|
|
@@ -60,7 +61,7 @@ program
|
|
|
60
61
|
program
|
|
61
62
|
.command("status")
|
|
62
63
|
.description("Show local Agent Profiler setup and ingest status.")
|
|
63
|
-
.option("--mode <mode>", "status mode: dev or prod", "dev")
|
|
64
|
+
.option("--mode <mode>", "status mode: dev or prod (prod expects a global install on PATH)", "dev")
|
|
64
65
|
.option("--json", "Output status as JSON")
|
|
65
66
|
.action((options) => {
|
|
66
67
|
if (options.mode !== "dev" && options.mode !== "prod") {
|
package/dist/commands/init.js
CHANGED
|
@@ -250,6 +250,10 @@ export function runInit(source, mode = "dev") {
|
|
|
250
250
|
console.log(`Config: ${configPath}`);
|
|
251
251
|
console.log(`Database: ${resolvedDbPath}`);
|
|
252
252
|
console.log(`Hooks: ${hooksPath}`);
|
|
253
|
+
if (mode === "prod") {
|
|
254
|
+
console.log("Note: Prod mode requires a real `agent-profiler` install on PATH.");
|
|
255
|
+
console.log(" Use `npx agent-profiler ...` for one-off commands only.");
|
|
256
|
+
}
|
|
253
257
|
if (usedFallbackConfigPath) {
|
|
254
258
|
console.log("Note: Could not write home config in this environment; wrote local config instead.");
|
|
255
259
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -105,19 +105,31 @@ function getCursorSetupStatus(configPath, mode) {
|
|
|
105
105
|
const sampleCommand = hookEntryCommand(hooks.beforeSubmitPrompt);
|
|
106
106
|
if (mode === "dev") {
|
|
107
107
|
if (!sampleCommand.startsWith("node ")) {
|
|
108
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
state: "partial",
|
|
110
|
+
note: "dev mode expects hooks to use `node <abs>/dist/cli.js ...`",
|
|
111
|
+
};
|
|
109
112
|
}
|
|
110
113
|
const cliPath = sampleCommand.split(" ")[1];
|
|
111
114
|
if (!cliPath || !fs.existsSync(cliPath)) {
|
|
112
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
state: "partial",
|
|
117
|
+
note: "dev hook CLI path is missing or invalid",
|
|
118
|
+
};
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
else {
|
|
116
122
|
if (!sampleCommand.startsWith("agent-profiler ")) {
|
|
117
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
state: "partial",
|
|
125
|
+
note: "prod mode expects hooks to use `agent-profiler ...` from a real install on PATH",
|
|
126
|
+
};
|
|
118
127
|
}
|
|
119
128
|
if (!commandExistsInPath("agent-profiler")) {
|
|
120
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
state: "partial",
|
|
131
|
+
note: "`agent-profiler` is not on PATH; `npx` only covers one-off commands",
|
|
132
|
+
};
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
135
|
return { state: "yes", note: "configured via init" };
|
|
@@ -148,23 +160,38 @@ function getCodexSetupStatus(configPath, mode) {
|
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
if (!sampleCommand) {
|
|
151
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
state: "partial",
|
|
165
|
+
note: "could not find agent-profiler command in Codex hooks",
|
|
166
|
+
};
|
|
152
167
|
}
|
|
153
168
|
if (mode === "dev") {
|
|
154
169
|
if (!sampleCommand.startsWith("node ")) {
|
|
155
|
-
return {
|
|
170
|
+
return {
|
|
171
|
+
state: "partial",
|
|
172
|
+
note: "dev mode expects hooks to use `node <abs>/dist/cli.js ...`",
|
|
173
|
+
};
|
|
156
174
|
}
|
|
157
175
|
const cliPath = sampleCommand.split(" ")[1];
|
|
158
176
|
if (!cliPath || !fs.existsSync(cliPath)) {
|
|
159
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
state: "partial",
|
|
179
|
+
note: "dev hook CLI path is missing or invalid",
|
|
180
|
+
};
|
|
160
181
|
}
|
|
161
182
|
}
|
|
162
183
|
else {
|
|
163
184
|
if (!sampleCommand.startsWith("agent-profiler ")) {
|
|
164
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
state: "partial",
|
|
187
|
+
note: "prod mode expects hooks to use `agent-profiler ...` from a real install on PATH",
|
|
188
|
+
};
|
|
165
189
|
}
|
|
166
190
|
if (!commandExistsInPath("agent-profiler")) {
|
|
167
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
state: "partial",
|
|
193
|
+
note: "`agent-profiler` is not on PATH; `npx` only covers one-off commands",
|
|
194
|
+
};
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
197
|
return { state: "yes", note: "configured via init" };
|
package/dist/core/db.js
CHANGED
|
@@ -56,27 +56,122 @@ export function applySchema(db) {
|
|
|
56
56
|
const schemaSql = fs.readFileSync(SCHEMA_PATH, "utf8");
|
|
57
57
|
db.exec(schemaSql);
|
|
58
58
|
migrateEventsSchema(db);
|
|
59
|
+
migrateInteractionSpansSchema(db);
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
-
const columns = db.prepare(`PRAGMA table_info(
|
|
61
|
+
function migrateTableColumns(db, tableName, columnsToAdd) {
|
|
62
|
+
const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
62
63
|
const names = new Set(columns.map((c) => c.name));
|
|
63
|
-
const
|
|
64
|
-
if (!names.has(
|
|
65
|
-
db.exec(sql);
|
|
66
|
-
names.add(
|
|
64
|
+
for (const column of columnsToAdd) {
|
|
65
|
+
if (!names.has(column.name)) {
|
|
66
|
+
db.exec(column.sql);
|
|
67
|
+
names.add(column.name);
|
|
67
68
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function migrateEventsSchema(db) {
|
|
72
|
+
migrateTableColumns(db, "events", [
|
|
73
|
+
{ name: "workspace_path", sql: `ALTER TABLE events ADD COLUMN workspace_path TEXT` },
|
|
74
|
+
{
|
|
75
|
+
name: "workspace_home_rel_path",
|
|
76
|
+
sql: `ALTER TABLE events ADD COLUMN workspace_home_rel_path TEXT`,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "workspace_display_path",
|
|
80
|
+
sql: `ALTER TABLE events ADD COLUMN workspace_display_path TEXT`,
|
|
81
|
+
},
|
|
82
|
+
{ name: "git_repo_root", sql: `ALTER TABLE events ADD COLUMN git_repo_root TEXT` },
|
|
83
|
+
{
|
|
84
|
+
name: "git_repo_root_home_rel_path",
|
|
85
|
+
sql: `ALTER TABLE events ADD COLUMN git_repo_root_home_rel_path TEXT`,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "git_repo_root_display_path",
|
|
89
|
+
sql: `ALTER TABLE events ADD COLUMN git_repo_root_display_path TEXT`,
|
|
90
|
+
},
|
|
91
|
+
{ name: "git_repo_name", sql: `ALTER TABLE events ADD COLUMN git_repo_name TEXT` },
|
|
92
|
+
{ name: "git_branch", sql: `ALTER TABLE events ADD COLUMN git_branch TEXT` },
|
|
93
|
+
{
|
|
94
|
+
name: "interaction_kind",
|
|
95
|
+
sql: `ALTER TABLE events ADD COLUMN interaction_kind TEXT`,
|
|
96
|
+
},
|
|
97
|
+
{ name: "correlation_id", sql: `ALTER TABLE events ADD COLUMN correlation_id TEXT` },
|
|
98
|
+
{
|
|
99
|
+
name: "tool_canonical_name",
|
|
100
|
+
sql: `ALTER TABLE events ADD COLUMN tool_canonical_name TEXT`,
|
|
101
|
+
},
|
|
102
|
+
{ name: "mcp_server", sql: `ALTER TABLE events ADD COLUMN mcp_server TEXT` },
|
|
103
|
+
{ name: "mcp_tool", sql: `ALTER TABLE events ADD COLUMN mcp_tool TEXT` },
|
|
104
|
+
{
|
|
105
|
+
name: "payload_byte_length",
|
|
106
|
+
sql: `ALTER TABLE events ADD COLUMN payload_byte_length INTEGER`,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "prompt_fingerprint",
|
|
110
|
+
sql: `ALTER TABLE events ADD COLUMN prompt_fingerprint TEXT`,
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
function migrateInteractionSpansSchema(db) {
|
|
115
|
+
migrateTableColumns(db, "interaction_spans", [
|
|
116
|
+
{ name: "turn_id", sql: `ALTER TABLE interaction_spans ADD COLUMN turn_id TEXT` },
|
|
117
|
+
{
|
|
118
|
+
name: "tool_canonical_name",
|
|
119
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN tool_canonical_name TEXT`,
|
|
120
|
+
},
|
|
121
|
+
{ name: "mcp_server", sql: `ALTER TABLE interaction_spans ADD COLUMN mcp_server TEXT` },
|
|
122
|
+
{ name: "mcp_tool", sql: `ALTER TABLE interaction_spans ADD COLUMN mcp_tool TEXT` },
|
|
123
|
+
{
|
|
124
|
+
name: "pre_event_id",
|
|
125
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN pre_event_id INTEGER`,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "post_event_id",
|
|
129
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN post_event_id INTEGER`,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "failure_event_id",
|
|
133
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN failure_event_id INTEGER`,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "arg_token_estimate",
|
|
137
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN arg_token_estimate INTEGER DEFAULT 0`,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "result_token_estimate",
|
|
141
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN result_token_estimate INTEGER DEFAULT 0`,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "workspace_path",
|
|
145
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN workspace_path TEXT`,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "workspace_home_rel_path",
|
|
149
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN workspace_home_rel_path TEXT`,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "workspace_display_path",
|
|
153
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN workspace_display_path TEXT`,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "git_repo_root",
|
|
157
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN git_repo_root TEXT`,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "git_repo_root_home_rel_path",
|
|
161
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN git_repo_root_home_rel_path TEXT`,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "git_repo_root_display_path",
|
|
165
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN git_repo_root_display_path TEXT`,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "git_repo_name",
|
|
169
|
+
sql: `ALTER TABLE interaction_spans ADD COLUMN git_repo_name TEXT`,
|
|
170
|
+
},
|
|
171
|
+
{ name: "git_branch", sql: `ALTER TABLE interaction_spans ADD COLUMN git_branch TEXT` },
|
|
172
|
+
{ name: "started_at", sql: `ALTER TABLE interaction_spans ADD COLUMN started_at TEXT` },
|
|
173
|
+
{ name: "completed_at", sql: `ALTER TABLE interaction_spans ADD COLUMN completed_at TEXT` },
|
|
174
|
+
]);
|
|
80
175
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_workspace ON events(workspace_path, created_at)`);
|
|
81
176
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_interaction_kind ON events(interaction_kind, created_at)`);
|
|
82
177
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_correlation ON events(correlation_id, session_id)`);
|
|
@@ -85,6 +180,9 @@ function migrateEventsSchema(db) {
|
|
|
85
180
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spans_session ON interaction_spans(session_key, started_at)`);
|
|
86
181
|
}
|
|
87
182
|
export function insertEvent(db, event, payloadHash, workspaceGit, derived) {
|
|
183
|
+
// Keep raw payloads verbatim for forensics and stable hashes; path redaction is a
|
|
184
|
+
// separate privacy decision from the derived `~/...` metadata stored alongside them.
|
|
185
|
+
const rawPayloadJson = JSON.stringify(event.rawPayload);
|
|
88
186
|
const stmt = db.prepare(`
|
|
89
187
|
INSERT INTO events (
|
|
90
188
|
created_at,
|
|
@@ -101,7 +199,11 @@ export function insertEvent(db, event, payloadHash, workspaceGit, derived) {
|
|
|
101
199
|
payload_hash,
|
|
102
200
|
raw_payload,
|
|
103
201
|
workspace_path,
|
|
202
|
+
workspace_home_rel_path,
|
|
203
|
+
workspace_display_path,
|
|
104
204
|
git_repo_root,
|
|
205
|
+
git_repo_root_home_rel_path,
|
|
206
|
+
git_repo_root_display_path,
|
|
105
207
|
git_repo_name,
|
|
106
208
|
git_branch,
|
|
107
209
|
interaction_kind,
|
|
@@ -112,9 +214,9 @@ export function insertEvent(db, event, payloadHash, workspaceGit, derived) {
|
|
|
112
214
|
payload_byte_length,
|
|
113
215
|
prompt_fingerprint
|
|
114
216
|
)
|
|
115
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
217
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
116
218
|
`);
|
|
117
|
-
const info = stmt.run(new Date().toISOString(), event.source, event.sourceEvent, event.repoPath ?? null, event.sessionId ?? null, event.turnId ?? null, event.model ?? null, event.role, event.estimatedInputTokens, event.estimatedOutputTokens, event.estimatedTotalTokens, payloadHash,
|
|
219
|
+
const info = stmt.run(new Date().toISOString(), event.source, event.sourceEvent, event.repoPath ?? null, event.sessionId ?? null, event.turnId ?? null, event.model ?? null, event.role, event.estimatedInputTokens, event.estimatedOutputTokens, event.estimatedTotalTokens, payloadHash, rawPayloadJson, workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, derived.interactionKind, derived.correlationId, derived.toolCanonicalName, derived.mcpServer, derived.mcpTool, derived.payloadByteLength, derived.promptFingerprint);
|
|
118
220
|
return Number(info.lastInsertRowid);
|
|
119
221
|
}
|
|
120
222
|
/**
|
|
@@ -143,19 +245,21 @@ export function mergeInteractionSpan(db, eventId, normalized, workspaceGit, deri
|
|
|
143
245
|
tool_canonical_name, mcp_server, mcp_tool,
|
|
144
246
|
pre_event_id, post_event_id, failure_event_id,
|
|
145
247
|
arg_token_estimate, result_token_estimate,
|
|
146
|
-
workspace_path,
|
|
248
|
+
workspace_path, workspace_home_rel_path, workspace_display_path,
|
|
249
|
+
git_repo_root, git_repo_root_home_rel_path, git_repo_root_display_path,
|
|
250
|
+
git_repo_name, git_branch,
|
|
147
251
|
started_at, completed_at
|
|
148
252
|
)
|
|
149
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
253
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
150
254
|
`);
|
|
151
255
|
if (derived.toolPhase === "pre") {
|
|
152
|
-
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, eventId, null, null, argTok, 0, workspaceGit.workspacePath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoName, workspaceGit.gitBranch, now, null);
|
|
256
|
+
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, eventId, null, null, argTok, 0, workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, now, null);
|
|
153
257
|
}
|
|
154
258
|
else if (derived.toolPhase === "post") {
|
|
155
|
-
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, null, eventId, null, 0, resTok, workspaceGit.workspacePath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoName, workspaceGit.gitBranch, null, now);
|
|
259
|
+
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, null, eventId, null, 0, resTok, workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, null, now);
|
|
156
260
|
}
|
|
157
261
|
else {
|
|
158
|
-
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, null, null, eventId, 0, resTok, workspaceGit.workspacePath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoName, workspaceGit.gitBranch, null, now);
|
|
262
|
+
ins.run(sessionKey, source, derived.correlationId, normalized.turnId ?? null, toolName, mcpS, mcpT, null, null, eventId, 0, resTok, workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, null, now);
|
|
159
263
|
}
|
|
160
264
|
return;
|
|
161
265
|
}
|
|
@@ -164,33 +268,57 @@ export function mergeInteractionSpan(db, eventId, normalized, workspaceGit, deri
|
|
|
164
268
|
pre_event_id = COALESCE(pre_event_id, ?),
|
|
165
269
|
started_at = COALESCE(started_at, ?),
|
|
166
270
|
arg_token_estimate = ?,
|
|
271
|
+
workspace_path = COALESCE(workspace_path, ?),
|
|
272
|
+
workspace_home_rel_path = COALESCE(workspace_home_rel_path, ?),
|
|
273
|
+
workspace_display_path = COALESCE(workspace_display_path, ?),
|
|
274
|
+
git_repo_root = COALESCE(git_repo_root, ?),
|
|
275
|
+
git_repo_root_home_rel_path = COALESCE(git_repo_root_home_rel_path, ?),
|
|
276
|
+
git_repo_root_display_path = COALESCE(git_repo_root_display_path, ?),
|
|
277
|
+
git_repo_name = COALESCE(git_repo_name, ?),
|
|
278
|
+
git_branch = COALESCE(git_branch, ?),
|
|
167
279
|
tool_canonical_name = COALESCE(tool_canonical_name, ?),
|
|
168
280
|
mcp_server = COALESCE(mcp_server, ?),
|
|
169
281
|
mcp_tool = COALESCE(mcp_tool, ?),
|
|
170
282
|
turn_id = COALESCE(turn_id, ?)
|
|
171
|
-
WHERE id = ?`).run(eventId, now, Math.max(existing.arg_token_estimate, argTok), toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
283
|
+
WHERE id = ?`).run(eventId, now, Math.max(existing.arg_token_estimate, argTok), workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
172
284
|
}
|
|
173
285
|
else if (derived.toolPhase === "post") {
|
|
174
286
|
db.prepare(`UPDATE interaction_spans SET
|
|
175
287
|
post_event_id = COALESCE(post_event_id, ?),
|
|
176
288
|
completed_at = COALESCE(completed_at, ?),
|
|
177
289
|
result_token_estimate = ?,
|
|
290
|
+
workspace_path = COALESCE(workspace_path, ?),
|
|
291
|
+
workspace_home_rel_path = COALESCE(workspace_home_rel_path, ?),
|
|
292
|
+
workspace_display_path = COALESCE(workspace_display_path, ?),
|
|
293
|
+
git_repo_root = COALESCE(git_repo_root, ?),
|
|
294
|
+
git_repo_root_home_rel_path = COALESCE(git_repo_root_home_rel_path, ?),
|
|
295
|
+
git_repo_root_display_path = COALESCE(git_repo_root_display_path, ?),
|
|
296
|
+
git_repo_name = COALESCE(git_repo_name, ?),
|
|
297
|
+
git_branch = COALESCE(git_branch, ?),
|
|
178
298
|
tool_canonical_name = COALESCE(tool_canonical_name, ?),
|
|
179
299
|
mcp_server = COALESCE(mcp_server, ?),
|
|
180
300
|
mcp_tool = COALESCE(mcp_tool, ?),
|
|
181
301
|
turn_id = COALESCE(turn_id, ?)
|
|
182
|
-
WHERE id = ?`).run(eventId, now, Math.max(existing.result_token_estimate, resTok), toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
302
|
+
WHERE id = ?`).run(eventId, now, Math.max(existing.result_token_estimate, resTok), workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
183
303
|
}
|
|
184
304
|
else {
|
|
185
305
|
db.prepare(`UPDATE interaction_spans SET
|
|
186
306
|
failure_event_id = COALESCE(failure_event_id, ?),
|
|
187
307
|
completed_at = COALESCE(completed_at, ?),
|
|
188
308
|
result_token_estimate = ?,
|
|
309
|
+
workspace_path = COALESCE(workspace_path, ?),
|
|
310
|
+
workspace_home_rel_path = COALESCE(workspace_home_rel_path, ?),
|
|
311
|
+
workspace_display_path = COALESCE(workspace_display_path, ?),
|
|
312
|
+
git_repo_root = COALESCE(git_repo_root, ?),
|
|
313
|
+
git_repo_root_home_rel_path = COALESCE(git_repo_root_home_rel_path, ?),
|
|
314
|
+
git_repo_root_display_path = COALESCE(git_repo_root_display_path, ?),
|
|
315
|
+
git_repo_name = COALESCE(git_repo_name, ?),
|
|
316
|
+
git_branch = COALESCE(git_branch, ?),
|
|
189
317
|
tool_canonical_name = COALESCE(tool_canonical_name, ?),
|
|
190
318
|
mcp_server = COALESCE(mcp_server, ?),
|
|
191
319
|
mcp_tool = COALESCE(mcp_tool, ?),
|
|
192
320
|
turn_id = COALESCE(turn_id, ?)
|
|
193
|
-
WHERE id = ?`).run(eventId, now, Math.max(existing.result_token_estimate, resTok), toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
321
|
+
WHERE id = ?`).run(eventId, now, Math.max(existing.result_token_estimate, resTok), workspaceGit.workspacePath, workspaceGit.workspaceHomeRelPath, workspaceGit.workspaceDisplayPath, workspaceGit.gitRepoRoot, workspaceGit.gitRepoRootHomeRelPath, workspaceGit.gitRepoRootDisplayPath, workspaceGit.gitRepoName, workspaceGit.gitBranch, toolName, mcpS, mcpT, normalized.turnId ?? null, existing.id);
|
|
194
322
|
}
|
|
195
323
|
}
|
|
196
324
|
export function getLastEventSummary(db) {
|
|
@@ -237,7 +365,11 @@ export function getEventsForLatestSession(db) {
|
|
|
237
365
|
estimated_total_tokens AS estimatedTotalTokens,
|
|
238
366
|
raw_payload AS rawPayload,
|
|
239
367
|
workspace_path AS workspacePath,
|
|
368
|
+
workspace_home_rel_path AS workspaceHomeRelPath,
|
|
369
|
+
workspace_display_path AS workspaceDisplayPath,
|
|
240
370
|
git_repo_root AS gitRepoRoot,
|
|
371
|
+
git_repo_root_home_rel_path AS gitRepoRootHomeRelPath,
|
|
372
|
+
git_repo_root_display_path AS gitRepoRootDisplayPath,
|
|
241
373
|
git_repo_name AS gitRepoName,
|
|
242
374
|
git_branch AS gitBranch
|
|
243
375
|
FROM events
|
|
@@ -262,7 +394,11 @@ export function getEventsForLatestSession(db) {
|
|
|
262
394
|
estimated_total_tokens AS estimatedTotalTokens,
|
|
263
395
|
raw_payload AS rawPayload,
|
|
264
396
|
workspace_path AS workspacePath,
|
|
397
|
+
workspace_home_rel_path AS workspaceHomeRelPath,
|
|
398
|
+
workspace_display_path AS workspaceDisplayPath,
|
|
265
399
|
git_repo_root AS gitRepoRoot,
|
|
400
|
+
git_repo_root_home_rel_path AS gitRepoRootHomeRelPath,
|
|
401
|
+
git_repo_root_display_path AS gitRepoRootDisplayPath,
|
|
266
402
|
git_repo_name AS gitRepoName,
|
|
267
403
|
git_branch AS gitBranch
|
|
268
404
|
FROM events
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os from "node:os";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { spawnSync } from "node:child_process";
|
|
3
4
|
function trimmedOrNull(value) {
|
|
@@ -12,6 +13,35 @@ function asRecord(value) {
|
|
|
12
13
|
}
|
|
13
14
|
return {};
|
|
14
15
|
}
|
|
16
|
+
function toSlashPath(relativePath) {
|
|
17
|
+
return relativePath.split(path.sep).join("/");
|
|
18
|
+
}
|
|
19
|
+
function deriveHomePath(absolutePath) {
|
|
20
|
+
if (!absolutePath) {
|
|
21
|
+
return { homeRelPath: null, displayPath: null };
|
|
22
|
+
}
|
|
23
|
+
const homeDir = trimmedOrNull(os.homedir());
|
|
24
|
+
if (!homeDir) {
|
|
25
|
+
return { homeRelPath: null, displayPath: null };
|
|
26
|
+
}
|
|
27
|
+
const normalizedHome = path.normalize(homeDir);
|
|
28
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
29
|
+
const relativePath = path.relative(normalizedHome, normalizedPath);
|
|
30
|
+
if (!relativePath) {
|
|
31
|
+
return { homeRelPath: ".", displayPath: "~" };
|
|
32
|
+
}
|
|
33
|
+
if (path.isAbsolute(relativePath)) {
|
|
34
|
+
return { homeRelPath: null, displayPath: null };
|
|
35
|
+
}
|
|
36
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) {
|
|
37
|
+
return { homeRelPath: null, displayPath: null };
|
|
38
|
+
}
|
|
39
|
+
const slashPath = toSlashPath(relativePath);
|
|
40
|
+
return {
|
|
41
|
+
homeRelPath: slashPath,
|
|
42
|
+
displayPath: `~/${slashPath}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
15
45
|
/**
|
|
16
46
|
* Absolute path for “where this event ran”: adapter repoPath, then common payload cwd keys, then process.cwd().
|
|
17
47
|
*/
|
|
@@ -52,22 +82,33 @@ function gitOutput(workspacePath, args) {
|
|
|
52
82
|
* Best-effort git context for `workspacePath`. Non-repo → git* fields null; `workspacePath` still set.
|
|
53
83
|
*/
|
|
54
84
|
export function resolveWorkspaceGitMeta(workspacePath) {
|
|
55
|
-
const
|
|
85
|
+
const normalizedWorkspacePath = path.normalize(workspacePath);
|
|
86
|
+
const workspaceHomePath = deriveHomePath(normalizedWorkspacePath);
|
|
87
|
+
const inside = gitOutput(normalizedWorkspacePath, ["rev-parse", "--is-inside-work-tree"]);
|
|
56
88
|
if (inside !== "true") {
|
|
57
89
|
return {
|
|
58
|
-
workspacePath,
|
|
90
|
+
workspacePath: normalizedWorkspacePath,
|
|
91
|
+
workspaceHomeRelPath: workspaceHomePath.homeRelPath,
|
|
92
|
+
workspaceDisplayPath: workspaceHomePath.displayPath,
|
|
59
93
|
gitRepoRoot: null,
|
|
94
|
+
gitRepoRootHomeRelPath: null,
|
|
95
|
+
gitRepoRootDisplayPath: null,
|
|
60
96
|
gitRepoName: null,
|
|
61
97
|
gitBranch: null,
|
|
62
98
|
};
|
|
63
99
|
}
|
|
64
|
-
const root = gitOutput(
|
|
65
|
-
const branch = gitOutput(
|
|
100
|
+
const root = gitOutput(normalizedWorkspacePath, ["rev-parse", "--show-toplevel"]);
|
|
101
|
+
const branch = gitOutput(normalizedWorkspacePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
66
102
|
const gitRepoRoot = root ? path.normalize(root) : null;
|
|
103
|
+
const gitRepoRootHomePath = deriveHomePath(gitRepoRoot);
|
|
67
104
|
const gitRepoName = gitRepoRoot ? path.basename(gitRepoRoot) : null;
|
|
68
105
|
return {
|
|
69
|
-
workspacePath,
|
|
106
|
+
workspacePath: normalizedWorkspacePath,
|
|
107
|
+
workspaceHomeRelPath: workspaceHomePath.homeRelPath,
|
|
108
|
+
workspaceDisplayPath: workspaceHomePath.displayPath,
|
|
70
109
|
gitRepoRoot,
|
|
110
|
+
gitRepoRootHomeRelPath: gitRepoRootHomePath.homeRelPath,
|
|
111
|
+
gitRepoRootDisplayPath: gitRepoRootHomePath.displayPath,
|
|
71
112
|
gitRepoName,
|
|
72
113
|
gitBranch: branch,
|
|
73
114
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
let cachedPackageMeta = null;
|
|
5
|
+
function readPackageMeta() {
|
|
6
|
+
if (cachedPackageMeta)
|
|
7
|
+
return cachedPackageMeta;
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
9
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
10
|
+
try {
|
|
11
|
+
cachedPackageMeta = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
cachedPackageMeta = {};
|
|
15
|
+
}
|
|
16
|
+
return cachedPackageMeta;
|
|
17
|
+
}
|
|
18
|
+
export function getPackageVersion() {
|
|
19
|
+
return readPackageMeta().version ?? "0.0.0";
|
|
20
|
+
}
|
package/dist/core/schema.sql
CHANGED
|
@@ -14,7 +14,11 @@ CREATE TABLE IF NOT EXISTS events (
|
|
|
14
14
|
payload_hash TEXT NOT NULL,
|
|
15
15
|
raw_payload TEXT NOT NULL,
|
|
16
16
|
workspace_path TEXT,
|
|
17
|
+
workspace_home_rel_path TEXT,
|
|
18
|
+
workspace_display_path TEXT,
|
|
17
19
|
git_repo_root TEXT,
|
|
20
|
+
git_repo_root_home_rel_path TEXT,
|
|
21
|
+
git_repo_root_display_path TEXT,
|
|
18
22
|
git_repo_name TEXT,
|
|
19
23
|
git_branch TEXT,
|
|
20
24
|
interaction_kind TEXT,
|
|
@@ -41,7 +45,11 @@ CREATE TABLE IF NOT EXISTS interaction_spans (
|
|
|
41
45
|
arg_token_estimate INTEGER DEFAULT 0,
|
|
42
46
|
result_token_estimate INTEGER DEFAULT 0,
|
|
43
47
|
workspace_path TEXT,
|
|
48
|
+
workspace_home_rel_path TEXT,
|
|
49
|
+
workspace_display_path TEXT,
|
|
44
50
|
git_repo_root TEXT,
|
|
51
|
+
git_repo_root_home_rel_path TEXT,
|
|
52
|
+
git_repo_root_display_path TEXT,
|
|
45
53
|
git_repo_name TEXT,
|
|
46
54
|
git_branch TEXT,
|
|
47
55
|
started_at TEXT,
|