@omercnet/paseo-context-mode 3.1.4-next.382.1 → 3.1.4-next.434.1
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/OVERVIEW.md +3 -3
- package/README.md +13 -15
- package/client/analytics-dashboard.tsx +745 -613
- package/client/controls.tsx +295 -0
- package/client/diagnostics-section.tsx +178 -0
- package/client/{context-mode-footer.tsx → footer.tsx} +22 -20
- package/client/knowledge-section.tsx +725 -697
- package/client/{context-mode-pill.tsx → pill.tsx} +5 -5
- package/client/scoped-knowledge-section.tsx +14 -20
- package/client/screen-scope.ts +12 -6
- package/client/settings-screen.tsx +94 -97
- package/client/surface.tsx +377 -0
- package/index.client.tsx +3 -3
- package/index.server.ts +18 -27
- package/npm-shrinkwrap.json +6 -1745
- package/package.json +3 -3
- package/server/actions.ts +91 -102
- package/server/analytics.ts +257 -308
- package/server/binary.ts +119 -94
- package/server/database.ts +463 -0
- package/server/handlers.ts +40 -95
- package/server/integration.ts +230 -361
- package/server/knowledge.ts +65 -137
- package/server/mcp-process.ts +135 -86
- package/server/storage-discovery.ts +46 -199
- package/shared/actions.ts +2 -27
- package/shared/analytics.ts +60 -33
- package/shared/contracts.ts +16 -47
- package/shared/knowledge.ts +62 -65
- package/shared/providers.ts +62 -0
- package/shared/settings.ts +33 -18
- package/client/actions-section.tsx +0 -621
- package/client/context-mode-surface.tsx +0 -444
- package/server/context-mode-db.ts +0 -481
- package/shared/index.ts +0 -5
package/server/analytics.ts
CHANGED
|
@@ -1,56 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import type {
|
|
4
|
+
AgentAnalytics,
|
|
3
5
|
AnalyticsByteAccounting,
|
|
4
6
|
AnalyticsDashboard,
|
|
5
7
|
AnalyticsWarning,
|
|
6
8
|
IndexedSourceAnalytics,
|
|
7
|
-
|
|
9
|
+
ProjectAnalytics,
|
|
10
|
+
SessionAnalytics,
|
|
8
11
|
} from "../shared/analytics";
|
|
12
|
+
import { CONTEXT_MODE_ANALYTICS_SCHEMA_VERSION } from "../shared/analytics";
|
|
9
13
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
CONTEXT_MODE_PROVIDERS,
|
|
15
|
+
IN_PROCESS_PROVIDERS,
|
|
16
|
+
providerForPlatform,
|
|
17
|
+
} from "../shared/providers";
|
|
13
18
|
import {
|
|
14
19
|
ContextModeDatabaseError,
|
|
15
20
|
type ContextModeDatabaseSnapshot,
|
|
21
|
+
type DatabaseSavings,
|
|
16
22
|
readContextModeDatabase,
|
|
17
|
-
|
|
18
|
-
} from "./
|
|
19
|
-
import { type ProviderConfigSource, readProviderOverrides } from "./integration";
|
|
23
|
+
} from "./database";
|
|
24
|
+
import { type ProviderOverrides, resolveProvider } from "./integration";
|
|
20
25
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type StorageDiscoveryResult,
|
|
26
|
+
type ContextModeProjectDatabase,
|
|
27
|
+
discoverContextModeDatabases,
|
|
28
|
+
resolveContextModeDataDir,
|
|
25
29
|
} from "./storage-discovery";
|
|
26
30
|
|
|
27
31
|
const MAX_WARNINGS = 20;
|
|
28
32
|
const MAX_SOURCES = 2_000;
|
|
29
|
-
const
|
|
33
|
+
const MAX_AGENTS = 32;
|
|
34
|
+
const MAX_PROJECTS = 50;
|
|
35
|
+
const MAX_RECENT_SESSIONS = 20;
|
|
30
36
|
const MAX_CATEGORIES = 128;
|
|
31
37
|
const MAX_DAYS = 400;
|
|
32
38
|
|
|
33
|
-
interface
|
|
34
|
-
eventDataBytes: number;
|
|
35
|
-
avoidedBytes: number;
|
|
36
|
-
returnedBytes: number;
|
|
37
|
-
snapshotBytes: number;
|
|
39
|
+
interface MutableBytes extends DatabaseSavings {
|
|
38
40
|
indexedBytes: number;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
interface
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
interface MutableAgent extends MutableBytes {
|
|
44
|
+
agent: string;
|
|
45
|
+
projects: number;
|
|
46
|
+
sessions: number;
|
|
45
47
|
indexedSources: number;
|
|
46
|
-
indexedChunks: number;
|
|
47
|
-
sessions: Set<string>;
|
|
48
|
-
projects: Set<string>;
|
|
49
|
-
bytes: MutableByteAccounting;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface MutableProviderAggregate extends MutableAggregate {
|
|
53
|
-
provider: string;
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
interface MutableSummary {
|
|
@@ -58,22 +52,16 @@ interface MutableSummary {
|
|
|
58
52
|
savedBytes: number;
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
interface
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
chunks: number;
|
|
65
|
-
codeChunks: number;
|
|
66
|
-
indexedBytes: number;
|
|
67
|
-
indexedAt: string | null;
|
|
68
|
-
state: "ready" | "empty" | "unavailable";
|
|
69
|
-
detail?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface ContextModeAnalyticsDependencies extends StorageDiscoveryOptions {
|
|
55
|
+
export interface ContextModeAnalyticsDependencies {
|
|
56
|
+
home?: string;
|
|
57
|
+
env?: NodeJS.ProcessEnv;
|
|
73
58
|
now?: () => Date;
|
|
74
|
-
|
|
75
|
-
listDatabases?: typeof listRegularContextModeDatabases;
|
|
59
|
+
discover?: typeof discoverContextModeDatabases;
|
|
76
60
|
readDatabase?: (path: string) => ContextModeDatabaseSnapshot;
|
|
61
|
+
/** Detected Context Mode runtime version; must resolve to null rather than reject. */
|
|
62
|
+
resolveVersion?: () => Promise<string | null>;
|
|
63
|
+
/** Paseo's configured providers; each may move its agents' data dir through its own env. */
|
|
64
|
+
providers?: ProviderOverrides;
|
|
77
65
|
}
|
|
78
66
|
|
|
79
67
|
class WarningCollector {
|
|
@@ -81,14 +69,13 @@ class WarningCollector {
|
|
|
81
69
|
#omitted = 0;
|
|
82
70
|
|
|
83
71
|
add(warning: AnalyticsWarning): void {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else this.#omitted++;
|
|
72
|
+
if (this.items.length < MAX_WARNINGS - 1) {
|
|
73
|
+
this.items.push({
|
|
74
|
+
...warning,
|
|
75
|
+
...(warning.project ? { project: warning.project.slice(0, 128) } : {}),
|
|
76
|
+
message: warning.message.slice(0, 512),
|
|
77
|
+
});
|
|
78
|
+
} else this.#omitted++;
|
|
92
79
|
}
|
|
93
80
|
|
|
94
81
|
finish(): AnalyticsWarning[] {
|
|
@@ -106,147 +93,66 @@ class WarningCollector {
|
|
|
106
93
|
}
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
function createAggregate(): MutableAggregate {
|
|
110
|
-
return {
|
|
111
|
-
databases: 0,
|
|
112
|
-
events: 0,
|
|
113
|
-
toolCalls: 0,
|
|
114
|
-
indexedSources: 0,
|
|
115
|
-
indexedChunks: 0,
|
|
116
|
-
sessions: new Set<string>(),
|
|
117
|
-
projects: new Set<string>(),
|
|
118
|
-
bytes: {
|
|
119
|
-
eventDataBytes: 0,
|
|
120
|
-
avoidedBytes: 0,
|
|
121
|
-
returnedBytes: 0,
|
|
122
|
-
snapshotBytes: 0,
|
|
123
|
-
indexedBytes: 0,
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
96
|
function boundedAdd(left: number, right: number): number {
|
|
129
97
|
return Math.min(Number.MAX_SAFE_INTEGER, left + right);
|
|
130
98
|
}
|
|
131
99
|
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
target.avoidedBytes = boundedAdd(target.avoidedBytes, source.avoidedBytes ?? 0);
|
|
135
|
-
target.returnedBytes = boundedAdd(target.returnedBytes, source.returnedBytes ?? 0);
|
|
136
|
-
target.snapshotBytes = boundedAdd(target.snapshotBytes, source.snapshotBytes ?? 0);
|
|
137
|
-
target.indexedBytes = boundedAdd(target.indexedBytes, source.indexedBytes ?? 0);
|
|
100
|
+
function emptyBytes(): MutableBytes {
|
|
101
|
+
return { calls: 0, producedBytes: 0, returnedBytes: 0, savedBytes: 0, indexedBytes: 0 };
|
|
138
102
|
}
|
|
139
103
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
function byteAccounting(bytes: MutableByteAccounting): AnalyticsByteAccounting {
|
|
148
|
-
return { ...bytes, savedBytes: savedBytes(bytes) };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function tokenFields(bytes: MutableByteAccounting): {
|
|
152
|
-
inputTokens: number;
|
|
153
|
-
outputTokens: number;
|
|
154
|
-
savedTokens: number;
|
|
155
|
-
savingsPercent: number;
|
|
156
|
-
} {
|
|
157
|
-
const saved = savedBytes(bytes);
|
|
158
|
-
const possibleInput = boundedAdd(saved, bytes.returnedBytes);
|
|
159
|
-
return {
|
|
160
|
-
inputTokens: Math.floor(possibleInput / 4),
|
|
161
|
-
outputTokens: Math.floor(bytes.returnedBytes / 4),
|
|
162
|
-
savedTokens: Math.floor(saved / 4),
|
|
163
|
-
savingsPercent: possibleInput > 0 ? Math.round((saved / possibleInput) * 1_000) / 10 : 0,
|
|
164
|
-
};
|
|
104
|
+
function addBytes(target: MutableBytes, source: MutableBytes): void {
|
|
105
|
+
target.calls = boundedAdd(target.calls, source.calls);
|
|
106
|
+
target.producedBytes = boundedAdd(target.producedBytes, source.producedBytes);
|
|
107
|
+
target.returnedBytes = boundedAdd(target.returnedBytes, source.returnedBytes);
|
|
108
|
+
target.savedBytes = boundedAdd(target.savedBytes, source.savedBytes);
|
|
109
|
+
target.indexedBytes = boundedAdd(target.indexedBytes, source.indexedBytes);
|
|
165
110
|
}
|
|
166
111
|
|
|
167
112
|
function incrementSummary(
|
|
168
113
|
summaries: Map<string, MutableSummary>,
|
|
169
114
|
key: string,
|
|
170
115
|
count: number,
|
|
171
|
-
|
|
116
|
+
savedBytes: number,
|
|
172
117
|
): void {
|
|
173
118
|
const current = summaries.get(key) ?? { count: 0, savedBytes: 0 };
|
|
174
119
|
current.count = boundedAdd(current.count, count);
|
|
175
|
-
current.savedBytes = boundedAdd(current.savedBytes,
|
|
120
|
+
current.savedBytes = boundedAdd(current.savedBytes, savedBytes);
|
|
176
121
|
summaries.set(key, current);
|
|
177
122
|
}
|
|
178
123
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
target.events = boundedAdd(target.events, snapshot.events);
|
|
187
|
-
target.toolCalls = boundedAdd(target.toolCalls, snapshot.toolCalls);
|
|
188
|
-
addBytes(target.bytes, snapshot);
|
|
189
|
-
for (const session of snapshot.sessions) target.sessions.add(`${sessionNamespace}\0${session}`);
|
|
190
|
-
for (const project of snapshot.projects) target.projects.add(project);
|
|
191
|
-
if (categoryTotals) {
|
|
192
|
-
for (const category of snapshot.byCategory) {
|
|
193
|
-
incrementSummary(categoryTotals, category.category, category.count, category.savedBytes);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (dayTotals) {
|
|
197
|
-
for (const day of snapshot.byDay) {
|
|
198
|
-
incrementSummary(dayTotals, day.date, day.calls, day.savedBytes);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function mergeSource(target: Map<string, MutableSource>, source: MutableSource): void {
|
|
204
|
-
const key = `${source.provider}\0${source.source}`;
|
|
205
|
-
const current = target.get(key);
|
|
206
|
-
if (!current) {
|
|
207
|
-
target.set(key, source);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
current.chunks = boundedAdd(current.chunks, source.chunks);
|
|
211
|
-
current.codeChunks = boundedAdd(current.codeChunks, source.codeChunks);
|
|
212
|
-
current.indexedBytes = boundedAdd(current.indexedBytes, source.indexedBytes);
|
|
213
|
-
if (source.indexedAt && (!current.indexedAt || source.indexedAt > current.indexedAt)) {
|
|
214
|
-
current.indexedAt = source.indexedAt;
|
|
215
|
-
}
|
|
216
|
-
if (source.state === "ready") current.state = "ready";
|
|
217
|
-
else if (source.state === "unavailable" && current.state === "empty")
|
|
218
|
-
current.state = "unavailable";
|
|
219
|
-
if (!current.detail && source.detail) current.detail = source.detail;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function providerOutput(aggregate: MutableProviderAggregate): ProviderAnalytics {
|
|
124
|
+
/** Token estimate at 4 bytes per token; reduction is saved over produced, as `ctx_stats` reports. */
|
|
125
|
+
function savingsFields(bytes: MutableBytes): {
|
|
126
|
+
savedTokens: number;
|
|
127
|
+
savingsPercent: number;
|
|
128
|
+
byteAccounting: AnalyticsByteAccounting;
|
|
129
|
+
} {
|
|
130
|
+
const reduction = bytes.producedBytes > 0 ? bytes.savedBytes / bytes.producedBytes : 0;
|
|
223
131
|
return {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
...tokenFields(aggregate.bytes),
|
|
233
|
-
byteAccounting: byteAccounting(aggregate.bytes),
|
|
132
|
+
savedTokens: Math.floor(bytes.savedBytes / 4),
|
|
133
|
+
savingsPercent: Math.min(100, Math.round(reduction * 1_000) / 10),
|
|
134
|
+
byteAccounting: {
|
|
135
|
+
producedBytes: bytes.producedBytes,
|
|
136
|
+
returnedBytes: bytes.returnedBytes,
|
|
137
|
+
savedBytes: bytes.savedBytes,
|
|
138
|
+
indexedBytes: bytes.indexedBytes,
|
|
139
|
+
},
|
|
234
140
|
};
|
|
235
141
|
}
|
|
236
142
|
|
|
237
|
-
function
|
|
143
|
+
function laterTimestamp(left: string | null, right: string | null): string | null {
|
|
144
|
+
if (!left) return right;
|
|
145
|
+
if (!right) return left;
|
|
146
|
+
return right > left ? right : left;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function databaseWarning(project: string, error: unknown): AnalyticsWarning {
|
|
238
150
|
if (error instanceof ContextModeDatabaseError) {
|
|
239
|
-
return {
|
|
240
|
-
code: error.code,
|
|
241
|
-
provider,
|
|
242
|
-
database: error.database,
|
|
243
|
-
message: error.message,
|
|
244
|
-
};
|
|
151
|
+
return { code: error.code, project: error.project, message: error.message };
|
|
245
152
|
}
|
|
246
153
|
return {
|
|
247
154
|
code: "database-query-failed",
|
|
248
|
-
|
|
249
|
-
database: basename(path),
|
|
155
|
+
project,
|
|
250
156
|
message: "The database could not be inspected.",
|
|
251
157
|
};
|
|
252
158
|
}
|
|
@@ -254,118 +160,158 @@ function databaseWarning(provider: string, path: string, error: unknown): Analyt
|
|
|
254
160
|
export async function collectContextModeAnalytics(
|
|
255
161
|
dependencies: ContextModeAnalyticsDependencies = {},
|
|
256
162
|
): Promise<AnalyticsDashboard> {
|
|
257
|
-
const
|
|
258
|
-
const listDatabases = dependencies.listDatabases ?? listRegularContextModeDatabases;
|
|
163
|
+
const discover = dependencies.discover ?? discoverContextModeDatabases;
|
|
259
164
|
const readDatabase = dependencies.readDatabase ?? readContextModeDatabase;
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
providers
|
|
265
|
-
|
|
266
|
-
|
|
165
|
+
const env = dependencies.env ?? process.env;
|
|
166
|
+
const home = dependencies.home ?? homedir();
|
|
167
|
+
const providers = dependencies.providers ?? {};
|
|
168
|
+
const dataDirOf = (id: string) =>
|
|
169
|
+
resolveContextModeDataDir({ ...env, ...resolveProvider(id, providers).env }, home);
|
|
170
|
+
// Symlinked aliases of one directory must not count its databases twice.
|
|
171
|
+
const canonical = (dir: string) => realpath(dir).catch(() => dir);
|
|
172
|
+
// A configured provider's own CONTEXT_MODE_DATA_DIR is where its injected agents write.
|
|
173
|
+
const dataDirs = new Set(
|
|
174
|
+
await Promise.all(
|
|
175
|
+
[resolveContextModeDataDir(env, home), ...Object.keys(providers).map(dataDirOf)].map(
|
|
176
|
+
canonical,
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
);
|
|
180
|
+
// Resume and Forget run through a provider, so each row names one that reaches its store,
|
|
181
|
+
// preferring the provider that recorded it.
|
|
182
|
+
const actionable = [
|
|
183
|
+
...CONTEXT_MODE_PROVIDERS.map((entry) => entry.provider),
|
|
184
|
+
...IN_PROCESS_PROVIDERS.map((entry) => entry.provider),
|
|
185
|
+
...Object.keys(providers).filter((id) => {
|
|
186
|
+
const { base, platform } = resolveProvider(id, providers);
|
|
187
|
+
return platform !== null || IN_PROCESS_PROVIDERS.some((entry) => entry.provider === base);
|
|
188
|
+
}),
|
|
189
|
+
];
|
|
190
|
+
const storeOf = new Map(
|
|
191
|
+
await Promise.all(actionable.map(async (id) => [id, await canonical(dataDirOf(id))] as const)),
|
|
192
|
+
);
|
|
193
|
+
const storeProvider = (agent: string, dataDir: string): string | null => {
|
|
194
|
+
const own = providerForPlatform(agent)?.provider;
|
|
195
|
+
const candidates = own ? [own, ...actionable] : actionable;
|
|
196
|
+
return candidates.find((id) => storeOf.get(id) === dataDir) ?? null;
|
|
197
|
+
};
|
|
198
|
+
const warnings = new WarningCollector();
|
|
199
|
+
const databases: (ContextModeProjectDatabase & { dataDir: string })[] = [];
|
|
200
|
+
for (const dataDir of dataDirs) {
|
|
201
|
+
const discovery = await discover(dataDir);
|
|
202
|
+
databases.push(...discovery.databases.map((database) => ({ ...database, dataDir })));
|
|
203
|
+
for (const warning of discovery.warnings) warnings.add(warning);
|
|
204
|
+
}
|
|
267
205
|
|
|
268
|
-
const total =
|
|
269
|
-
|
|
206
|
+
const total = emptyBytes();
|
|
207
|
+
let sessions = 0;
|
|
208
|
+
let events = 0;
|
|
209
|
+
let indexedSources = 0;
|
|
210
|
+
let indexedChunks = 0;
|
|
211
|
+
let lastActivityAt: string | null = null;
|
|
212
|
+
const agents = new Map<string, MutableAgent>();
|
|
213
|
+
const projects: ProjectAnalytics[] = [];
|
|
270
214
|
const categories = new Map<string, MutableSummary>();
|
|
271
215
|
const days = new Map<string, MutableSummary>();
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
let
|
|
275
|
-
|
|
276
|
-
for (const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
] as const) {
|
|
284
|
-
const listing = await listDatabases(root.provider, directory);
|
|
285
|
-
for (const warning of listing.warnings) warningCollector.add(warning);
|
|
286
|
-
|
|
287
|
-
for (const path of listing.files) {
|
|
288
|
-
attemptedDatabases++;
|
|
289
|
-
try {
|
|
290
|
-
const snapshot = readDatabase(path);
|
|
291
|
-
successfulDatabases++;
|
|
292
|
-
total.databases = boundedAdd(total.databases, 1);
|
|
293
|
-
provider.databases = boundedAdd(provider.databases, 1);
|
|
294
|
-
|
|
295
|
-
if (snapshot.kind === "session") {
|
|
296
|
-
mergeSessionSnapshot(total, snapshot, root.provider, categories, days);
|
|
297
|
-
mergeSessionSnapshot(provider, snapshot, root.provider);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
total.indexedSources = boundedAdd(total.indexedSources, snapshot.sources.length);
|
|
302
|
-
total.indexedChunks = boundedAdd(total.indexedChunks, snapshot.chunks);
|
|
303
|
-
provider.indexedSources = boundedAdd(provider.indexedSources, snapshot.sources.length);
|
|
304
|
-
provider.indexedChunks = boundedAdd(provider.indexedChunks, snapshot.chunks);
|
|
305
|
-
addBytes(total.bytes, { indexedBytes: snapshot.indexedBytes });
|
|
306
|
-
addBytes(provider.bytes, { indexedBytes: snapshot.indexedBytes });
|
|
307
|
-
for (const day of snapshot.byDay) {
|
|
308
|
-
incrementSummary(days, day.date, day.calls, day.savedBytes);
|
|
309
|
-
}
|
|
310
|
-
for (const source of snapshot.sources) {
|
|
311
|
-
mergeSource(sourceMap, {
|
|
312
|
-
provider: root.provider,
|
|
313
|
-
source: source.source,
|
|
314
|
-
chunks: source.chunks,
|
|
315
|
-
codeChunks: source.codeChunks,
|
|
316
|
-
indexedBytes: source.indexedBytes,
|
|
317
|
-
indexedAt: source.indexedAt,
|
|
318
|
-
state: source.chunks > 0 ? "ready" : "empty",
|
|
319
|
-
...(source.detail ? { detail: source.detail } : {}),
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
for (const message of snapshot.inconsistencies) {
|
|
323
|
-
warningCollector.add({
|
|
324
|
-
code: "inconsistent-index",
|
|
325
|
-
provider: root.provider,
|
|
326
|
-
database: basename(path),
|
|
327
|
-
message,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
} catch (error) {
|
|
331
|
-
const warning = databaseWarning(root.provider, path, error);
|
|
332
|
-
warningCollector.add(warning);
|
|
333
|
-
if (directoryKind === "content") {
|
|
334
|
-
mergeSource(sourceMap, {
|
|
335
|
-
provider: root.provider,
|
|
336
|
-
source: `[database] ${basename(path)}`,
|
|
337
|
-
chunks: 0,
|
|
338
|
-
codeChunks: 0,
|
|
339
|
-
indexedBytes: 0,
|
|
340
|
-
indexedAt: null,
|
|
341
|
-
state: "unavailable",
|
|
342
|
-
detail: warning.message,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
216
|
+
const sources: IndexedSourceAnalytics[] = [];
|
|
217
|
+
const recentSessions: SessionAnalytics[] = [];
|
|
218
|
+
let unsupportedSchema = false;
|
|
219
|
+
|
|
220
|
+
for (const database of databases) {
|
|
221
|
+
let snapshot: ContextModeDatabaseSnapshot;
|
|
222
|
+
try {
|
|
223
|
+
snapshot = readDatabase(database.path);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error instanceof ContextModeDatabaseError && error.code === "unsupported-schema") {
|
|
226
|
+
unsupportedSchema = true;
|
|
346
227
|
}
|
|
228
|
+
warnings.add(databaseWarning(database.project, error));
|
|
229
|
+
continue;
|
|
347
230
|
}
|
|
348
|
-
}
|
|
349
231
|
|
|
350
|
-
|
|
351
|
-
(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
232
|
+
addBytes(total, snapshot);
|
|
233
|
+
sessions = boundedAdd(sessions, snapshot.sessions);
|
|
234
|
+
events = boundedAdd(events, snapshot.events);
|
|
235
|
+
indexedSources = boundedAdd(indexedSources, snapshot.indexedSources);
|
|
236
|
+
indexedChunks = boundedAdd(indexedChunks, snapshot.indexedChunks);
|
|
237
|
+
lastActivityAt = laterTimestamp(lastActivityAt, snapshot.lastActivityAt);
|
|
238
|
+
projects.push({
|
|
239
|
+
path: snapshot.worktrees[0] ?? database.project,
|
|
240
|
+
worktrees: snapshot.worktrees.length,
|
|
241
|
+
sessions: snapshot.sessions,
|
|
242
|
+
toolCalls: snapshot.calls,
|
|
243
|
+
indexedSources: snapshot.indexedSources,
|
|
244
|
+
...savingsFields(snapshot),
|
|
245
|
+
lastActivityAt: snapshot.lastActivityAt,
|
|
358
246
|
});
|
|
247
|
+
|
|
248
|
+
for (const summary of snapshot.byAgent) {
|
|
249
|
+
const agent = agents.get(summary.agent) ?? {
|
|
250
|
+
agent: summary.agent,
|
|
251
|
+
projects: 0,
|
|
252
|
+
sessions: 0,
|
|
253
|
+
indexedSources: 0,
|
|
254
|
+
...emptyBytes(),
|
|
255
|
+
};
|
|
256
|
+
agent.projects = boundedAdd(agent.projects, 1);
|
|
257
|
+
agent.sessions = boundedAdd(agent.sessions, summary.sessions);
|
|
258
|
+
agent.indexedSources = boundedAdd(agent.indexedSources, summary.indexedSources);
|
|
259
|
+
addBytes(agent, summary);
|
|
260
|
+
agents.set(summary.agent, agent);
|
|
261
|
+
}
|
|
262
|
+
for (const tool of snapshot.byTool) {
|
|
263
|
+
incrementSummary(categories, tool.tool, tool.calls, tool.savedBytes);
|
|
264
|
+
}
|
|
265
|
+
for (const day of snapshot.byDay) incrementSummary(days, day.date, day.calls, day.savedBytes);
|
|
266
|
+
for (const source of snapshot.sources) {
|
|
267
|
+
sources.push({
|
|
268
|
+
agent: source.agent,
|
|
269
|
+
projectPath: source.projectPath,
|
|
270
|
+
source: source.label,
|
|
271
|
+
kind: source.kind,
|
|
272
|
+
chunks: source.chunks,
|
|
273
|
+
indexedBytes: source.bytes,
|
|
274
|
+
indexedAt: source.createdAt,
|
|
275
|
+
provider: storeProvider(source.agent, database.dataDir),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
recentSessions.push(
|
|
279
|
+
...snapshot.sessionList.map((session) => ({
|
|
280
|
+
...session,
|
|
281
|
+
provider: storeProvider(session.agent, database.dataDir),
|
|
282
|
+
})),
|
|
283
|
+
);
|
|
284
|
+
for (const message of snapshot.truncated) {
|
|
285
|
+
warnings.add({ code: "result-truncated", project: database.project, message });
|
|
286
|
+
}
|
|
359
287
|
}
|
|
360
|
-
|
|
361
|
-
const
|
|
288
|
+
|
|
289
|
+
const allAgents = [...agents.values()]
|
|
290
|
+
.sort(
|
|
291
|
+
(left, right) => right.savedBytes - left.savedBytes || left.agent.localeCompare(right.agent),
|
|
292
|
+
)
|
|
293
|
+
.map(
|
|
294
|
+
(agent): AgentAnalytics => ({
|
|
295
|
+
agent: agent.agent,
|
|
296
|
+
projects: agent.projects,
|
|
297
|
+
sessions: agent.sessions,
|
|
298
|
+
toolCalls: agent.calls,
|
|
299
|
+
indexedSources: agent.indexedSources,
|
|
300
|
+
...savingsFields(agent),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
362
303
|
const allCategories = [...categories]
|
|
363
304
|
.map(([category, summary]) => ({
|
|
364
305
|
category,
|
|
365
306
|
count: summary.count,
|
|
366
307
|
savedTokens: Math.floor(summary.savedBytes / 4),
|
|
367
308
|
}))
|
|
368
|
-
.sort(
|
|
309
|
+
.sort(
|
|
310
|
+
(left, right) =>
|
|
311
|
+
right.savedTokens - left.savedTokens ||
|
|
312
|
+
right.count - left.count ||
|
|
313
|
+
left.category.localeCompare(right.category),
|
|
314
|
+
);
|
|
369
315
|
const allDays = [...days]
|
|
370
316
|
.map(([date, summary]) => ({
|
|
371
317
|
date,
|
|
@@ -373,64 +319,67 @@ export async function collectContextModeAnalytics(
|
|
|
373
319
|
savedTokens: Math.floor(summary.savedBytes / 4),
|
|
374
320
|
}))
|
|
375
321
|
.sort((left, right) => left.date.localeCompare(right.date));
|
|
322
|
+
sources.sort(
|
|
323
|
+
(left, right) =>
|
|
324
|
+
(right.indexedAt ?? "").localeCompare(left.indexedAt ?? "") ||
|
|
325
|
+
left.source.localeCompare(right.source),
|
|
326
|
+
);
|
|
327
|
+
recentSessions.sort(
|
|
328
|
+
(left, right) =>
|
|
329
|
+
(right.lastActivityAt ?? "").localeCompare(left.lastActivityAt ?? "") ||
|
|
330
|
+
left.sessionId.localeCompare(right.sessionId),
|
|
331
|
+
);
|
|
332
|
+
projects.sort(
|
|
333
|
+
(left, right) =>
|
|
334
|
+
right.byteAccounting.savedBytes - left.byteAccounting.savedBytes ||
|
|
335
|
+
left.path.localeCompare(right.path),
|
|
336
|
+
);
|
|
376
337
|
for (const [count, maximum, label] of [
|
|
377
|
-
[
|
|
378
|
-
[allCategories.length, MAX_CATEGORIES, "
|
|
338
|
+
[allAgents.length, MAX_AGENTS, "agent"],
|
|
339
|
+
[allCategories.length, MAX_CATEGORIES, "tool"],
|
|
379
340
|
[allDays.length, MAX_DAYS, "daily"],
|
|
341
|
+
[sources.length, MAX_SOURCES, "indexed source"],
|
|
342
|
+
[projects.length, MAX_PROJECTS, "project"],
|
|
380
343
|
] as const) {
|
|
381
344
|
if (count > maximum) {
|
|
382
|
-
|
|
345
|
+
warnings.add({
|
|
383
346
|
code: "result-truncated",
|
|
384
347
|
message: `Only the first ${maximum} ${label} analytics rows are included.`,
|
|
385
348
|
});
|
|
386
349
|
}
|
|
387
350
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
: hadWarnings
|
|
396
|
-
? "partial"
|
|
397
|
-
: "complete";
|
|
398
|
-
const warnings = warningCollector.finish();
|
|
351
|
+
|
|
352
|
+
// A locked or unopenable store is a transient gap; only a schema mismatch warrants "update Context Mode".
|
|
353
|
+
const unsupported = projects.length === 0 && unsupportedSchema;
|
|
354
|
+
const coverage = warnings.hasWarnings ? "partial" : "complete";
|
|
355
|
+
const contextModeVersion = dependencies.resolveVersion
|
|
356
|
+
? await dependencies.resolveVersion()
|
|
357
|
+
: null;
|
|
399
358
|
|
|
400
359
|
return {
|
|
401
360
|
schemaVersion: CONTEXT_MODE_ANALYTICS_SCHEMA_VERSION,
|
|
402
|
-
contextModeVersion
|
|
403
|
-
completeness,
|
|
404
|
-
warnings,
|
|
361
|
+
contextModeVersion,
|
|
362
|
+
completeness: unsupported ? "unsupported" : coverage,
|
|
363
|
+
warnings: warnings.finish(),
|
|
405
364
|
totals: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
365
|
+
agents: agents.size,
|
|
366
|
+
projects: projects.length,
|
|
367
|
+
sessions,
|
|
368
|
+
events,
|
|
369
|
+
toolCalls: total.calls,
|
|
370
|
+
indexedSources,
|
|
371
|
+
indexedChunks,
|
|
372
|
+
inputTokens: Math.floor(total.producedBytes / 4),
|
|
373
|
+
outputTokens: Math.floor(total.returnedBytes / 4),
|
|
374
|
+
...savingsFields(total),
|
|
375
|
+
lastActivityAt,
|
|
416
376
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
377
|
+
byAgent: allAgents.slice(0, MAX_AGENTS),
|
|
378
|
+
byProject: projects.slice(0, MAX_PROJECTS),
|
|
379
|
+
recentSessions: recentSessions.slice(0, MAX_RECENT_SESSIONS),
|
|
380
|
+
byCategory: allCategories.slice(0, MAX_CATEGORIES),
|
|
381
|
+
byDay: allDays.slice(-MAX_DAYS),
|
|
382
|
+
sources: sources.slice(0, MAX_SOURCES),
|
|
421
383
|
generatedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
|
|
422
384
|
};
|
|
423
385
|
}
|
|
424
|
-
|
|
425
|
-
export function createContextModeAnalyticsHandler(
|
|
426
|
-
dependencies: ContextModeAnalyticsDependencies = {},
|
|
427
|
-
): (
|
|
428
|
-
_input: Record<string, never>,
|
|
429
|
-
context?: { paseo?: ProviderConfigSource },
|
|
430
|
-
) => Promise<AnalyticsDashboard> {
|
|
431
|
-
return async (_input, context) =>
|
|
432
|
-
collectContextModeAnalytics({
|
|
433
|
-
...dependencies,
|
|
434
|
-
providers: dependencies.providers ?? (await readProviderOverrides(context?.paseo)),
|
|
435
|
-
});
|
|
436
|
-
}
|