@productbrain/mcp 0.0.1-beta.47 → 0.0.1-beta.4721
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/.env.mcp.example +4 -0
- package/README.md +67 -0
- package/dist/chunk-DSSH6AT2.js +1676 -0
- package/dist/chunk-DSSH6AT2.js.map +1 -0
- package/dist/chunk-JIGCLBNA.js +17051 -0
- package/dist/chunk-JIGCLBNA.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +1204 -117
- package/dist/http.js.map +1 -1
- package/dist/index.js +8 -14
- package/dist/index.js.map +1 -1
- package/dist/{setup-LSCFKMW7.js → setup-PRDF4HMP.js} +25 -15
- package/dist/setup-PRDF4HMP.js.map +1 -0
- package/dist/views/src/entry-cards/index.html +252 -0
- package/dist/views/src/graph-constellation/index.html +279 -0
- package/package.json +8 -3
- package/dist/chunk-FMEZXUP5.js +0 -10519
- package/dist/chunk-FMEZXUP5.js.map +0 -1
- package/dist/chunk-FYFF4QKF.js +0 -2646
- package/dist/chunk-FYFF4QKF.js.map +0 -1
- package/dist/chunk-MRIO53BY.js +0 -243
- package/dist/chunk-MRIO53BY.js.map +0 -1
- package/dist/setup-LSCFKMW7.js.map +0 -1
- package/dist/smart-capture-XLBFE252.js +0 -36
- package/dist/smart-capture-XLBFE252.js.map +0 -1
|
@@ -0,0 +1,1676 @@
|
|
|
1
|
+
// src/analytics.ts
|
|
2
|
+
import { userInfo } from "os";
|
|
3
|
+
import { PostHog } from "posthog-node";
|
|
4
|
+
var client = null;
|
|
5
|
+
var distinctId = "anonymous";
|
|
6
|
+
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
7
|
+
function log(msg) {
|
|
8
|
+
if (process.env.MCP_DEBUG === "1") {
|
|
9
|
+
process.stderr.write(msg);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function getBuildTimeKey() {
|
|
13
|
+
try {
|
|
14
|
+
return "";
|
|
15
|
+
} catch {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function initAnalytics() {
|
|
20
|
+
const apiKey = process.env.POSTHOG_MCP_KEY || getBuildTimeKey();
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
log("[MCP-ANALYTICS] No PostHog key \u2014 tracking disabled (set SYNERGYOS_POSTHOG_KEY at build time for publish)\n");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
client = new PostHog(apiKey, {
|
|
26
|
+
host: POSTHOG_HOST,
|
|
27
|
+
flushAt: 1,
|
|
28
|
+
flushInterval: 5e3,
|
|
29
|
+
featureFlagsPollingInterval: 3e4
|
|
30
|
+
});
|
|
31
|
+
distinctId = process.env.MCP_USER_ID || fallbackDistinctId();
|
|
32
|
+
log(`[MCP-ANALYTICS] Initialized \u2014 host=${POSTHOG_HOST} distinctId=${distinctId}
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
function fallbackDistinctId() {
|
|
36
|
+
try {
|
|
37
|
+
return userInfo().username;
|
|
38
|
+
} catch {
|
|
39
|
+
return `os-${process.pid}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function trackSessionStarted(workspaceId, serverVersion) {
|
|
43
|
+
if (!client) return;
|
|
44
|
+
client.capture({
|
|
45
|
+
distinctId,
|
|
46
|
+
event: "mcp_session_started",
|
|
47
|
+
properties: {
|
|
48
|
+
workspace_id: workspaceId,
|
|
49
|
+
server_version: serverVersion,
|
|
50
|
+
source: "mcp-server",
|
|
51
|
+
$groups: { workspace: workspaceId }
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function trackToolCall(fn, status, durationMs, workspaceId, errorMsg) {
|
|
56
|
+
const properties = {
|
|
57
|
+
tool: fn,
|
|
58
|
+
status,
|
|
59
|
+
duration_ms: durationMs,
|
|
60
|
+
workspace_id: workspaceId,
|
|
61
|
+
source: "mcp-server",
|
|
62
|
+
$groups: { workspace: workspaceId }
|
|
63
|
+
};
|
|
64
|
+
if (errorMsg) properties.error = errorMsg;
|
|
65
|
+
if (!client) return;
|
|
66
|
+
client.capture({
|
|
67
|
+
distinctId,
|
|
68
|
+
event: "mcp_tool_called",
|
|
69
|
+
properties
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function trackCompoundToolAction(tool, action, workspaceId) {
|
|
73
|
+
if (!client) return;
|
|
74
|
+
client.capture({
|
|
75
|
+
distinctId,
|
|
76
|
+
event: "mcp_compound_tool_action",
|
|
77
|
+
properties: {
|
|
78
|
+
tool,
|
|
79
|
+
action: action ?? null,
|
|
80
|
+
workspace_id: workspaceId,
|
|
81
|
+
source: "mcp-server",
|
|
82
|
+
$groups: { workspace: workspaceId }
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function trackSetupStarted() {
|
|
87
|
+
if (!client) return;
|
|
88
|
+
client.capture({
|
|
89
|
+
distinctId,
|
|
90
|
+
event: "mcp_setup_started",
|
|
91
|
+
properties: {
|
|
92
|
+
source: "mcp-server",
|
|
93
|
+
platform: process.platform
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function trackSetupCompleted(chosenClient, outcome) {
|
|
98
|
+
if (!client) return;
|
|
99
|
+
client.capture({
|
|
100
|
+
distinctId,
|
|
101
|
+
event: "mcp_setup_completed",
|
|
102
|
+
properties: {
|
|
103
|
+
client: chosenClient,
|
|
104
|
+
outcome,
|
|
105
|
+
source: "mcp-server",
|
|
106
|
+
platform: process.platform
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function trackQualityVerdict(workspaceId, props) {
|
|
111
|
+
if (!client) return;
|
|
112
|
+
client.capture({
|
|
113
|
+
distinctId,
|
|
114
|
+
event: "quality_verdict_generated",
|
|
115
|
+
properties: {
|
|
116
|
+
...props,
|
|
117
|
+
workspace_id: workspaceId,
|
|
118
|
+
source_system: "mcp-server",
|
|
119
|
+
$groups: { workspace: workspaceId }
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function trackQualityCheck(workspaceId, props) {
|
|
124
|
+
if (!client) return;
|
|
125
|
+
client.capture({
|
|
126
|
+
distinctId,
|
|
127
|
+
event: "quality_verdict_checked",
|
|
128
|
+
properties: {
|
|
129
|
+
...props,
|
|
130
|
+
workspace_id: workspaceId,
|
|
131
|
+
source_system: "mcp-server",
|
|
132
|
+
$groups: { workspace: workspaceId }
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function trackCaptureClassifierEvent(event, workspaceId, props) {
|
|
137
|
+
if (!client) return;
|
|
138
|
+
try {
|
|
139
|
+
client.capture({
|
|
140
|
+
distinctId,
|
|
141
|
+
event,
|
|
142
|
+
properties: {
|
|
143
|
+
...props,
|
|
144
|
+
workspace_id: workspaceId,
|
|
145
|
+
source_system: "mcp-server",
|
|
146
|
+
$groups: { workspace: workspaceId }
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function trackCaptureClassifierEvaluated(workspaceId, props) {
|
|
153
|
+
trackCaptureClassifierEvent("mcp_capture_classifier_evaluated", workspaceId, props);
|
|
154
|
+
}
|
|
155
|
+
function trackCaptureClassifierAutoRouted(workspaceId, props) {
|
|
156
|
+
trackCaptureClassifierEvent("mcp_capture_classifier_auto_routed", workspaceId, props);
|
|
157
|
+
}
|
|
158
|
+
function trackCaptureClassifierFallback(workspaceId, props) {
|
|
159
|
+
trackCaptureClassifierEvent("mcp_capture_classifier_fallback", workspaceId, props);
|
|
160
|
+
}
|
|
161
|
+
function trackChainEntryCommitted(workspaceId, props) {
|
|
162
|
+
if (!client) return;
|
|
163
|
+
try {
|
|
164
|
+
client.capture({
|
|
165
|
+
distinctId,
|
|
166
|
+
event: "chain_entry_committed",
|
|
167
|
+
properties: {
|
|
168
|
+
workspace_id: workspaceId,
|
|
169
|
+
source_system: "mcp-server",
|
|
170
|
+
$groups: { workspace: workspaceId },
|
|
171
|
+
...props
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function trackKnowledgeGap(workspaceId, props) {
|
|
178
|
+
if (!client) return;
|
|
179
|
+
try {
|
|
180
|
+
client.capture({
|
|
181
|
+
distinctId,
|
|
182
|
+
event: "knowledge_gap_detected",
|
|
183
|
+
properties: {
|
|
184
|
+
...props,
|
|
185
|
+
query: props.query.slice(0, 200),
|
|
186
|
+
workspace_id: workspaceId,
|
|
187
|
+
source_system: "mcp-server",
|
|
188
|
+
$groups: { workspace: workspaceId }
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function trackCaptureQualityHints(workspaceId, props) {
|
|
195
|
+
if (!client) return;
|
|
196
|
+
try {
|
|
197
|
+
client.capture({
|
|
198
|
+
distinctId,
|
|
199
|
+
event: "mcp_capture_quality_hints",
|
|
200
|
+
properties: {
|
|
201
|
+
...props,
|
|
202
|
+
workspace_id: workspaceId,
|
|
203
|
+
source_system: "mcp-server",
|
|
204
|
+
$groups: { workspace: workspaceId }
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function trackCaptureRelationSuggestions(workspaceId, props) {
|
|
211
|
+
if (!client) return;
|
|
212
|
+
try {
|
|
213
|
+
client.capture({
|
|
214
|
+
distinctId,
|
|
215
|
+
event: "mcp_capture_relation_suggestions",
|
|
216
|
+
properties: {
|
|
217
|
+
...props,
|
|
218
|
+
workspace_id: workspaceId,
|
|
219
|
+
source_system: "mcp-server",
|
|
220
|
+
$groups: { workspace: workspaceId }
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function trackCollectionClassified(workspaceId, props) {
|
|
227
|
+
if (!client) return;
|
|
228
|
+
try {
|
|
229
|
+
client.capture({
|
|
230
|
+
distinctId,
|
|
231
|
+
event: "collection_classified",
|
|
232
|
+
properties: {
|
|
233
|
+
...props,
|
|
234
|
+
workspace_id: workspaceId,
|
|
235
|
+
source_system: "mcp-server",
|
|
236
|
+
$groups: { workspace: workspaceId }
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function trackFieldGuidanceApplied(workspaceId, props) {
|
|
243
|
+
if (!client) return;
|
|
244
|
+
try {
|
|
245
|
+
client.capture({
|
|
246
|
+
distinctId,
|
|
247
|
+
event: "field_guidance_applied",
|
|
248
|
+
properties: {
|
|
249
|
+
...props,
|
|
250
|
+
workspace_id: workspaceId,
|
|
251
|
+
source_system: "mcp-server",
|
|
252
|
+
$groups: { workspace: workspaceId }
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function trackFieldQualityWarning(workspaceId, props) {
|
|
259
|
+
if (!client) return;
|
|
260
|
+
try {
|
|
261
|
+
client.capture({
|
|
262
|
+
distinctId,
|
|
263
|
+
event: "field_quality_warning",
|
|
264
|
+
properties: {
|
|
265
|
+
...props,
|
|
266
|
+
workspace_id: workspaceId,
|
|
267
|
+
source_system: "mcp-server",
|
|
268
|
+
$groups: { workspace: workspaceId }
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function trackSessionCaptureRate(workspaceId, props) {
|
|
275
|
+
if (!client) return;
|
|
276
|
+
try {
|
|
277
|
+
client.capture({
|
|
278
|
+
distinctId,
|
|
279
|
+
event: "session_capture_rate",
|
|
280
|
+
properties: {
|
|
281
|
+
...props,
|
|
282
|
+
workspace_id: workspaceId,
|
|
283
|
+
source_system: "mcp-server",
|
|
284
|
+
$groups: { workspace: workspaceId }
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function trackZeroCaptureAuditFired(workspaceId, props) {
|
|
291
|
+
if (!client) return;
|
|
292
|
+
try {
|
|
293
|
+
client.capture({
|
|
294
|
+
distinctId,
|
|
295
|
+
event: "zero_capture_audit_fired",
|
|
296
|
+
properties: {
|
|
297
|
+
...props,
|
|
298
|
+
workspace_id: workspaceId,
|
|
299
|
+
source_system: "mcp-server",
|
|
300
|
+
$groups: { workspace: workspaceId }
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function trackCaptureContractMiss(workspaceId, props) {
|
|
307
|
+
if (!client) return;
|
|
308
|
+
try {
|
|
309
|
+
client.capture({
|
|
310
|
+
distinctId,
|
|
311
|
+
event: "capture_contract_miss",
|
|
312
|
+
properties: {
|
|
313
|
+
...props,
|
|
314
|
+
workspace_id: workspaceId,
|
|
315
|
+
source_system: "mcp-server",
|
|
316
|
+
$groups: { workspace: workspaceId }
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function trackWriteBackHintServed(workspaceId, props) {
|
|
323
|
+
if (!client) return;
|
|
324
|
+
try {
|
|
325
|
+
client.capture({
|
|
326
|
+
distinctId,
|
|
327
|
+
event: "write_back_hint_served",
|
|
328
|
+
properties: {
|
|
329
|
+
...props,
|
|
330
|
+
workspace_id: workspaceId,
|
|
331
|
+
source_system: "mcp-server",
|
|
332
|
+
$groups: { workspace: workspaceId }
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function trackCommitErrorByCode(workspaceId, props) {
|
|
339
|
+
if (!client) return;
|
|
340
|
+
try {
|
|
341
|
+
client.capture({
|
|
342
|
+
distinctId,
|
|
343
|
+
event: "commit_error_by_code",
|
|
344
|
+
properties: {
|
|
345
|
+
...props,
|
|
346
|
+
workspace_id: workspaceId,
|
|
347
|
+
source_system: "mcp-server",
|
|
348
|
+
$groups: { workspace: workspaceId }
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function trackClassifierDivergence(workspaceId, props) {
|
|
355
|
+
if (!client) return;
|
|
356
|
+
try {
|
|
357
|
+
client.capture({
|
|
358
|
+
distinctId,
|
|
359
|
+
event: "classifier_divergence",
|
|
360
|
+
properties: {
|
|
361
|
+
...props,
|
|
362
|
+
workspace_id: workspaceId,
|
|
363
|
+
source_system: "mcp-server",
|
|
364
|
+
$groups: { workspace: workspaceId }
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
} catch {
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function getPostHogClient() {
|
|
371
|
+
return client;
|
|
372
|
+
}
|
|
373
|
+
async function shutdownAnalytics() {
|
|
374
|
+
await client?.shutdown();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/auth.ts
|
|
378
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
379
|
+
import { createHash } from "crypto";
|
|
380
|
+
function hashKey(key) {
|
|
381
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
382
|
+
}
|
|
383
|
+
var requestStore = new AsyncLocalStorage();
|
|
384
|
+
function runWithAuth(auth, fn) {
|
|
385
|
+
return requestStore.run(auth, fn);
|
|
386
|
+
}
|
|
387
|
+
function getRequestApiKey() {
|
|
388
|
+
return requestStore.getStore()?.apiKey;
|
|
389
|
+
}
|
|
390
|
+
function getRequestMcpSessionId() {
|
|
391
|
+
return requestStore.getStore()?.mcpSessionId;
|
|
392
|
+
}
|
|
393
|
+
var SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
394
|
+
var MAX_KEYS = 100;
|
|
395
|
+
var keyStateMap = /* @__PURE__ */ new Map();
|
|
396
|
+
function newKeyState() {
|
|
397
|
+
return {
|
|
398
|
+
workspaceId: null,
|
|
399
|
+
workspaceSlug: null,
|
|
400
|
+
workspaceName: null,
|
|
401
|
+
workspaceCreatedAt: null,
|
|
402
|
+
workspaceGovernanceMode: null,
|
|
403
|
+
agentSessionId: null,
|
|
404
|
+
apiKeyId: null,
|
|
405
|
+
apiKeyScope: "readwrite",
|
|
406
|
+
sessionOriented: false,
|
|
407
|
+
sessionClosed: false,
|
|
408
|
+
lastAccess: Date.now(),
|
|
409
|
+
deploymentUrl: null
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function getKeyState(apiKey) {
|
|
413
|
+
let s = keyStateMap.get(apiKey);
|
|
414
|
+
if (!s) {
|
|
415
|
+
s = newKeyState();
|
|
416
|
+
keyStateMap.set(apiKey, s);
|
|
417
|
+
evictStale();
|
|
418
|
+
}
|
|
419
|
+
s.lastAccess = Date.now();
|
|
420
|
+
return s;
|
|
421
|
+
}
|
|
422
|
+
function evictStale() {
|
|
423
|
+
if (keyStateMap.size <= MAX_KEYS) return;
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
for (const [key, s] of keyStateMap) {
|
|
426
|
+
if (now - s.lastAccess > SESSION_TTL_MS) keyStateMap.delete(key);
|
|
427
|
+
}
|
|
428
|
+
if (keyStateMap.size > MAX_KEYS) {
|
|
429
|
+
const sorted = [...keyStateMap.entries()].sort((a, b) => a[1].lastAccess - b[1].lastAccess);
|
|
430
|
+
for (let i = 0; i < sorted.length - MAX_KEYS; i++) {
|
|
431
|
+
keyStateMap.delete(sorted[i][0]);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/cli/config-writer.ts
|
|
437
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
438
|
+
import { join, dirname } from "path";
|
|
439
|
+
import { homedir, platform } from "os";
|
|
440
|
+
var SERVER_ENTRY_KEY = "Product Brain";
|
|
441
|
+
var LEGACY_ENTRY_KEY = "productbrain";
|
|
442
|
+
var MCP_NPX_PACKAGE = "@productbrain/mcp@beta";
|
|
443
|
+
function buildServerEntry(apiKey) {
|
|
444
|
+
return {
|
|
445
|
+
command: "npx",
|
|
446
|
+
args: ["-y", MCP_NPX_PACKAGE],
|
|
447
|
+
env: { PRODUCTBRAIN_API_KEY: apiKey }
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function getCursorConfigPath() {
|
|
451
|
+
return join(process.cwd(), ".cursor", "mcp.json");
|
|
452
|
+
}
|
|
453
|
+
function getClaudeDesktopConfigPath() {
|
|
454
|
+
const os = platform();
|
|
455
|
+
if (os === "darwin") {
|
|
456
|
+
return join(
|
|
457
|
+
homedir(),
|
|
458
|
+
"Library",
|
|
459
|
+
"Application Support",
|
|
460
|
+
"Claude",
|
|
461
|
+
"claude_desktop_config.json"
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
if (os === "win32") {
|
|
465
|
+
const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
466
|
+
return join(appData, "Claude", "claude_desktop_config.json");
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
function resolveClient(name) {
|
|
471
|
+
if (name === "Cursor") {
|
|
472
|
+
return { name, configPath: getCursorConfigPath() };
|
|
473
|
+
}
|
|
474
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
475
|
+
return configPath ? { name, configPath } : null;
|
|
476
|
+
}
|
|
477
|
+
function readJsonSafe(path) {
|
|
478
|
+
if (!existsSync(path)) return {};
|
|
479
|
+
try {
|
|
480
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
481
|
+
} catch {
|
|
482
|
+
return {};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function writeClientConfig(client2, apiKey) {
|
|
486
|
+
const config = readJsonSafe(client2.configPath);
|
|
487
|
+
const serversKey = "mcpServers";
|
|
488
|
+
if (!config[serversKey]) config[serversKey] = {};
|
|
489
|
+
if (config[serversKey][LEGACY_ENTRY_KEY]) {
|
|
490
|
+
const legacy = config[serversKey][LEGACY_ENTRY_KEY];
|
|
491
|
+
config[serversKey][SERVER_ENTRY_KEY] = {
|
|
492
|
+
...buildServerEntry(apiKey),
|
|
493
|
+
env: { ...legacy.env, PRODUCTBRAIN_API_KEY: legacy.env?.PRODUCTBRAIN_API_KEY ?? apiKey }
|
|
494
|
+
};
|
|
495
|
+
delete config[serversKey][LEGACY_ENTRY_KEY];
|
|
496
|
+
} else {
|
|
497
|
+
const existing = config[serversKey][SERVER_ENTRY_KEY];
|
|
498
|
+
config[serversKey][SERVER_ENTRY_KEY] = existing ? { ...existing, env: { ...existing.env, PRODUCTBRAIN_API_KEY: apiKey } } : buildServerEntry(apiKey);
|
|
499
|
+
}
|
|
500
|
+
const dir = dirname(client2.configPath);
|
|
501
|
+
if (!existsSync(dir)) {
|
|
502
|
+
mkdirSync(dir, { recursive: true });
|
|
503
|
+
}
|
|
504
|
+
writeFileSync(client2.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/generated/routeLatencyBudget.generated.ts
|
|
509
|
+
var ROUTE_LATENCY_BUDGET_MS = {
|
|
510
|
+
query: 1e4,
|
|
511
|
+
mutation: 1e4,
|
|
512
|
+
action: 3e4
|
|
513
|
+
};
|
|
514
|
+
var ROUTE_LATENCY_BUDGET_OVERRIDE_MS = {
|
|
515
|
+
// convex/intelligence/onboardingChat.ts:333 declares `timeoutMs: 45_000` for the extraction
|
|
516
|
+
// LLM call — 15s BEYOND the flat action budget. 60s clears it with room for the surrounding
|
|
517
|
+
// request/response work the 45s covers none of.
|
|
518
|
+
"onboarding.chat": 6e4,
|
|
519
|
+
// convex/intelligence/spineCheck.ts:210 sets SPINE_CHECK_WALL_CLOCK_BUDGET_MS = 4 * 60_000
|
|
520
|
+
// for its SEQUENTIAL strategy probes (:225-239, deliberately not Promise.all), and :215
|
|
521
|
+
// reserves a tail of one 25s probe + an optional 25s story pass + 10s. The server's own
|
|
522
|
+
// design ceiling is scheduleSpineCheck's 5-min MAX_RUN_IN_FLIGHT_MS lease (:206-209).
|
|
523
|
+
// 5.5 min sits above that ceiling and far under Convex's 10-min action cap.
|
|
524
|
+
"quality.spineCheck": 33e4
|
|
525
|
+
};
|
|
526
|
+
var WIDEST_ROUTE_LATENCY_BUDGET_MS = Math.max(
|
|
527
|
+
...Object.values(ROUTE_LATENCY_BUDGET_MS),
|
|
528
|
+
...Object.values(ROUTE_LATENCY_BUDGET_OVERRIDE_MS)
|
|
529
|
+
);
|
|
530
|
+
var ROUTE_TYPE_ENTRIES = {
|
|
531
|
+
"resolveWorkspace": "query",
|
|
532
|
+
"feedback.submit": "mutation",
|
|
533
|
+
"feedback.listOwn": "query",
|
|
534
|
+
"feedback.list": "action",
|
|
535
|
+
"feedback.note": "action",
|
|
536
|
+
"feedback.group": "action",
|
|
537
|
+
"feedback.status": "action",
|
|
538
|
+
"organisation.status": "query",
|
|
539
|
+
"organisation.assignMembership": "mutation",
|
|
540
|
+
"organisation.removeMembership": "mutation",
|
|
541
|
+
"organisation.setOwnership": "mutation",
|
|
542
|
+
"organisation.clearOwnership": "mutation",
|
|
543
|
+
"organisation.activate": "mutation",
|
|
544
|
+
"organisation.upgrade": "mutation",
|
|
545
|
+
"organisation.upgradeRung2": "mutation",
|
|
546
|
+
"organisation.upgradeRung3": "mutation",
|
|
547
|
+
"organisation.upgradeRung4": "mutation",
|
|
548
|
+
"organisation.updateGovernanceMode": "mutation",
|
|
549
|
+
"organisation.hierarchy.show": "query",
|
|
550
|
+
"organisation.hierarchy.setParent": "mutation",
|
|
551
|
+
"organisation.hierarchy.clearParent": "mutation",
|
|
552
|
+
"acceptPolicy.approve": "mutation",
|
|
553
|
+
"acceptPolicy.retire": "mutation",
|
|
554
|
+
"acceptPolicy.list": "query",
|
|
555
|
+
"chain.seed": "mutation",
|
|
556
|
+
"chain.listCollections": "query",
|
|
557
|
+
"chain.getCollection": "query",
|
|
558
|
+
"chain.getCollectionFields": "query",
|
|
559
|
+
"chain.auditCollections": "query",
|
|
560
|
+
"chain.exportDefinitions": "query",
|
|
561
|
+
"chain.createCollection": "mutation",
|
|
562
|
+
"chain.updateCollection": "mutation",
|
|
563
|
+
"chain.listEntries": "query",
|
|
564
|
+
"chain.getEntry": "query",
|
|
565
|
+
"chain.getQualityVerdict": "query",
|
|
566
|
+
"chain.batchGetEntries": "query",
|
|
567
|
+
"chain.createEntry": "action",
|
|
568
|
+
"chain.updateEntry": "mutation",
|
|
569
|
+
"chain.restoreArchivedEntry": "mutation",
|
|
570
|
+
"chain.moveToCollection": "mutation",
|
|
571
|
+
"chain.shapeAdvisories": "query",
|
|
572
|
+
"chain.shapeAdvisorySummary": "query",
|
|
573
|
+
"chain.showShapeAdvisory": "query",
|
|
574
|
+
"chain.dispositionShapeAdvisory": "mutation",
|
|
575
|
+
"conflicts.list": "query",
|
|
576
|
+
"conflicts.resolve": "mutation",
|
|
577
|
+
"conflicts.summary": "query",
|
|
578
|
+
"conflicts.snooze": "mutation",
|
|
579
|
+
"conflicts.reverdict": "mutation",
|
|
580
|
+
"direction.list": "query",
|
|
581
|
+
"direction.refresh": "action",
|
|
582
|
+
"direction.defer": "mutation",
|
|
583
|
+
"question.create": "mutation",
|
|
584
|
+
"question.adopt": "mutation",
|
|
585
|
+
"question.assign": "mutation",
|
|
586
|
+
"question.snooze": "mutation",
|
|
587
|
+
"question.decline": "mutation",
|
|
588
|
+
"question.answer": "mutation",
|
|
589
|
+
"question.forceClose": "mutation",
|
|
590
|
+
"question.list": "query",
|
|
591
|
+
"chain.classifyCollection": "action",
|
|
592
|
+
"chain.classifyStrategyCategory": "query",
|
|
593
|
+
"chain.batchClassifyHeuristic": "query",
|
|
594
|
+
"chain.resolveCollection": "action",
|
|
595
|
+
"chain.searchEntries": "query",
|
|
596
|
+
"chain.searchByCanonicalName": "query",
|
|
597
|
+
"chain.commitEntry": "mutation",
|
|
598
|
+
"chain.verifyEntry": "mutation",
|
|
599
|
+
"chain.batchCommitConstellation": "mutation",
|
|
600
|
+
"chain.listEntryHistory": "query",
|
|
601
|
+
"chain.listEntryVersions": "query",
|
|
602
|
+
"chain.createEntryRelation": "mutation",
|
|
603
|
+
"chain.createEntryRelations": "mutation",
|
|
604
|
+
"chain.removeEntryRelation": "mutation",
|
|
605
|
+
"chain.normalizeEntryDataLLM": "action",
|
|
606
|
+
"chain.decomposeContent": "action",
|
|
607
|
+
"chain.normalizeEntryDataPreview": "action",
|
|
608
|
+
"chain.listEntryRelations": "query",
|
|
609
|
+
"chain.scoreLinkCandidates": "query",
|
|
610
|
+
"chain.evaluateCoherence": "query",
|
|
611
|
+
"chain.listAutoLinkSuggestions": "query",
|
|
612
|
+
"chain.acceptAutoLinkSuggestion": "mutation",
|
|
613
|
+
"chain.dismissAutoLinkSuggestion": "mutation",
|
|
614
|
+
"chain.quarantineAutoLinkSuggestion": "mutation",
|
|
615
|
+
"chain.resurrectAutoLinkSuggestion": "mutation",
|
|
616
|
+
"chain.expireAutoLinkSuggestion": "mutation",
|
|
617
|
+
"chain.clusterAutoLinkSuggestions": "query",
|
|
618
|
+
"chain.batchApplyAutoLinkSuggestions": "action",
|
|
619
|
+
"chain.validateCommitConstellation": "query",
|
|
620
|
+
"chain.getCaptureContract": "query",
|
|
621
|
+
"chain.gatherContext": "query",
|
|
622
|
+
"chain.getConstellation": "query",
|
|
623
|
+
"chain.auditBet": "query",
|
|
624
|
+
"agentKnowledge.facilitateEnvelope": "action",
|
|
625
|
+
"agentKnowledge.wrapupEnvelope": "action",
|
|
626
|
+
"agentKnowledge.captureEnvelope": "action",
|
|
627
|
+
"chain.graphSuggestLinks": "query",
|
|
628
|
+
"chain.graphGatherContext": "query",
|
|
629
|
+
"chain.assembleBuildContext": "query",
|
|
630
|
+
"chain.qualityCheck": "query",
|
|
631
|
+
"chain.changeDetection": "query",
|
|
632
|
+
"chain.structuralAggregation": "query",
|
|
633
|
+
"chain.detectSemanticConflicts": "action",
|
|
634
|
+
"chain.taskAwareGatherContext": "query",
|
|
635
|
+
"chain.journeyAwareGatherContext": "query",
|
|
636
|
+
"chain.resolveTaskStartup": "query",
|
|
637
|
+
"chain.getBindingGovernanceView": "query",
|
|
638
|
+
"chain.resolveTaskStartupHybrid": "action",
|
|
639
|
+
"chain.taskAwareHybridGatherContext": "action",
|
|
640
|
+
"chain.gatherFromSeeds": "query",
|
|
641
|
+
"chain.getEntryNeighborhood": "query",
|
|
642
|
+
"chain.deepChainWalk": "action",
|
|
643
|
+
"chain.recordBriefRun": "mutation",
|
|
644
|
+
"chain.getLastBriefRun": "query",
|
|
645
|
+
"chain.incrementalChanges": "query",
|
|
646
|
+
"chain.compoundQuery": "action",
|
|
647
|
+
"chain.dismissSuggestion": "mutation",
|
|
648
|
+
"chain.recordSessionSignal": "mutation",
|
|
649
|
+
"chain.workspaceReadiness": "query",
|
|
650
|
+
"chain.getCaptureHealth": "query",
|
|
651
|
+
"chain.getGroundingHealth": "query",
|
|
652
|
+
"chain.recordGroundingOutcome": "mutation",
|
|
653
|
+
"chain.suggestLinksForCapture": "query",
|
|
654
|
+
"chain.setOnboardingCompleted": "mutation",
|
|
655
|
+
"chain.checkCardinalityWarning": "query",
|
|
656
|
+
"chain.ingestDocument": "action",
|
|
657
|
+
"chain.getOrientEntries": "query",
|
|
658
|
+
"chain.getOrientView": "action",
|
|
659
|
+
"chain.getRitualsSurface": "query",
|
|
660
|
+
"chain.getGovernanceWithRelations": "action",
|
|
661
|
+
"chain.classifyGovernance": "query",
|
|
662
|
+
"chain.getVocabulary": "query",
|
|
663
|
+
"scoreboard.get": "action",
|
|
664
|
+
"rework.report": "mutation",
|
|
665
|
+
"chain.listLabels": "query",
|
|
666
|
+
"chain.createLabel": "mutation",
|
|
667
|
+
"chain.updateLabel": "mutation",
|
|
668
|
+
"chain.deleteLabel": "mutation",
|
|
669
|
+
"chain.applyLabel": "mutation",
|
|
670
|
+
"chain.removeLabel": "mutation",
|
|
671
|
+
"chain.listEntriesByLabel": "query",
|
|
672
|
+
"setup.getActiveSurface": "query",
|
|
673
|
+
"setup.materializeSetup": "action",
|
|
674
|
+
"setup.recordTamperRefusal": "mutation",
|
|
675
|
+
"setup.recordTransition": "mutation",
|
|
676
|
+
"setup.getCurrentSetupState": "query",
|
|
677
|
+
"setup.listAssetsForUser": "query",
|
|
678
|
+
"setup.ingestSetupAsset": "mutation",
|
|
679
|
+
"setup.ingestSetupAssetWithBody": "action",
|
|
680
|
+
"setup.fetchAssetBody": "action",
|
|
681
|
+
"setup.auditAssetBodies": "action",
|
|
682
|
+
"setup.repairAssetBody": "action",
|
|
683
|
+
"setup.listFailedAuditReceipts": "action",
|
|
684
|
+
"setup.markPersonalSetupAssetDormantFromSync": "mutation",
|
|
685
|
+
"setup.resolveSemanticRefs": "query",
|
|
686
|
+
"setup.updateLastProjectedHash": "mutation",
|
|
687
|
+
"setup.getSkillSystemHealth": "query",
|
|
688
|
+
"setup.recordActivationReceipt": "mutation",
|
|
689
|
+
"setup.recordSetupInvocation": "mutation",
|
|
690
|
+
"setup.getUserActivationState": "query",
|
|
691
|
+
"setup.getPbSetupState": "query",
|
|
692
|
+
"setup.getSkillBody": "action",
|
|
693
|
+
"setup.stampMcpOnlySurface": "mutation",
|
|
694
|
+
"setup.stampDetectedSurfaces": "mutation",
|
|
695
|
+
"setup.getKey32Snapshot": "query",
|
|
696
|
+
"setup.getKey33Snapshot": "query",
|
|
697
|
+
"gaps.record": "mutation",
|
|
698
|
+
"gaps.resolve": "mutation",
|
|
699
|
+
"gaps.top": "query",
|
|
700
|
+
"gaps.stats": "query",
|
|
701
|
+
"agent.startSession": "mutation",
|
|
702
|
+
"agent.resumeSession": "mutation",
|
|
703
|
+
"agent.closeSession": "mutation",
|
|
704
|
+
"agent.markOriented": "mutation",
|
|
705
|
+
"agent.touchSession": "mutation",
|
|
706
|
+
"agent.recordActivity": "mutation",
|
|
707
|
+
"agent.getSession": "query",
|
|
708
|
+
"agent.getActiveSession": "query",
|
|
709
|
+
"agent.recentSessions": "query",
|
|
710
|
+
"agent.activityStats": "query",
|
|
711
|
+
"agent.getActivityByDay": "query",
|
|
712
|
+
"agent.validateSession": "query",
|
|
713
|
+
"agent.getSessionWrapup": "query",
|
|
714
|
+
"agent.recordWrapup": "mutation",
|
|
715
|
+
"agent.reportOrientMetric": "mutation",
|
|
716
|
+
"agent.listSessions": "query",
|
|
717
|
+
"agent.showConversation": "query",
|
|
718
|
+
"usage.getWorkspaceSummary": "query",
|
|
719
|
+
"quality.evaluateHeuristicAndSchedule": "mutation",
|
|
720
|
+
"quality.reEvaluateEntry": "mutation",
|
|
721
|
+
"quality.evaluateAtCapture": "action",
|
|
722
|
+
"quality.evaluateAtCommit": "action",
|
|
723
|
+
"quality.evaluateForReview": "action",
|
|
724
|
+
"quality.getCachedVerdict": "query",
|
|
725
|
+
"quality.getLatestVerdictForEntry": "query",
|
|
726
|
+
"quality.spineCheck": "action",
|
|
727
|
+
"quality.scheduleSpineCheck": "mutation",
|
|
728
|
+
"quality.getLatestSpineVerdict": "query",
|
|
729
|
+
"onboarding.chat": "action",
|
|
730
|
+
"workspace.health": "query",
|
|
731
|
+
"workspace.healthAll": "query",
|
|
732
|
+
"workspace.backfill": "mutation",
|
|
733
|
+
"workspace.backfillAll": "mutation",
|
|
734
|
+
"authorityDomains.readiness": "query",
|
|
735
|
+
"authorityDomains.add": "mutation",
|
|
736
|
+
"authorityDomains.propose": "action",
|
|
737
|
+
"authorityDomains.review": "query",
|
|
738
|
+
"authorityDomains.queueKnownTag": "mutation",
|
|
739
|
+
"authorityDomains.ratify": "mutation",
|
|
740
|
+
"authorityDomains.reject": "mutation",
|
|
741
|
+
"authorityDomains.discardPending": "mutation",
|
|
742
|
+
"authorityDomains.recordSample": "mutation",
|
|
743
|
+
"authorityDomains.benchmark": "action",
|
|
744
|
+
"authorityDomains.activateCutover": "mutation",
|
|
745
|
+
"authorityDomains.principleDistribution": "query",
|
|
746
|
+
"performance.getVitalsSummary": "query",
|
|
747
|
+
"performance.getApiOverview": "query",
|
|
748
|
+
"performance.getRouteBreakdown": "query",
|
|
749
|
+
"performance.getSlowSamples": "query",
|
|
750
|
+
"performance.getSampleCount": "query",
|
|
751
|
+
"chainwork.listTypes": "query",
|
|
752
|
+
"chainwork.getChainType": "query",
|
|
753
|
+
"chainwork.scoreRun": "query",
|
|
754
|
+
"chainwork.getArtifact": "query",
|
|
755
|
+
"chainwork.getWorkflowRun": "query",
|
|
756
|
+
"chainwork.getLatestWorkflowRun": "query",
|
|
757
|
+
"chainwork.recordWorkflowCheckpoint": "mutation",
|
|
758
|
+
"chainwork.finalizeWorkflowRun": "action",
|
|
759
|
+
"chainwork.submitToKG": "action",
|
|
760
|
+
"chainwork.generate": "action",
|
|
761
|
+
"chainwork.getLastVerifiedBrief": "query",
|
|
762
|
+
"gitchain.createChain": "mutation",
|
|
763
|
+
"gitchain.editLink": "mutation",
|
|
764
|
+
"gitchain.updateChain": "mutation",
|
|
765
|
+
"gitchain.getChain": "query",
|
|
766
|
+
"gitchain.listChains": "query",
|
|
767
|
+
"gitchain.getHistory": "query",
|
|
768
|
+
"gitchain.listCommits": "query",
|
|
769
|
+
"gitchain.commitChain": "mutation",
|
|
770
|
+
"gitchain.diffVersions": "mutation",
|
|
771
|
+
"gitchain.runGate": "query",
|
|
772
|
+
"gitchain.createBranch": "mutation",
|
|
773
|
+
"gitchain.listBranches": "mutation",
|
|
774
|
+
"gitchain.checkConflicts": "mutation",
|
|
775
|
+
"gitchain.mergeBranch": "mutation",
|
|
776
|
+
"gitchain.addComment": "mutation",
|
|
777
|
+
"gitchain.resolveComment": "mutation",
|
|
778
|
+
"gitchain.listComments": "mutation",
|
|
779
|
+
"gitchain.revertChain": "mutation",
|
|
780
|
+
"staging.getCommittedSourceRefs": "query",
|
|
781
|
+
"staging.commitStagingEntryWithClassification": "action",
|
|
782
|
+
"governance.listProposals": "query",
|
|
783
|
+
"governance.countOpenProposals": "query",
|
|
784
|
+
"governance.respondToProposal": "mutation",
|
|
785
|
+
"maps.createMap": "mutation",
|
|
786
|
+
"maps.createAudienceMapSet": "mutation",
|
|
787
|
+
"maps.addToSlot": "mutation",
|
|
788
|
+
"maps.removeFromSlot": "mutation",
|
|
789
|
+
"maps.replaceInSlot": "mutation",
|
|
790
|
+
"maps.commitMap": "mutation",
|
|
791
|
+
"maps.getMap": "query",
|
|
792
|
+
"maps.listMaps": "query",
|
|
793
|
+
"maps.getJourneyMapWithEnrichment": "query",
|
|
794
|
+
"maps.listJourneyMaps": "query",
|
|
795
|
+
"maps.listMapCommits": "query"
|
|
796
|
+
};
|
|
797
|
+
var ROUTE_TYPE_BY_NAME = Object.assign(
|
|
798
|
+
/* @__PURE__ */ Object.create(null),
|
|
799
|
+
ROUTE_TYPE_ENTRIES
|
|
800
|
+
);
|
|
801
|
+
function latencyBudgetMsForRouteType(type) {
|
|
802
|
+
return ROUTE_LATENCY_BUDGET_MS[type];
|
|
803
|
+
}
|
|
804
|
+
function latencyBudgetMsForRoute(routeName) {
|
|
805
|
+
if (Object.hasOwn(ROUTE_LATENCY_BUDGET_OVERRIDE_MS, routeName)) {
|
|
806
|
+
return ROUTE_LATENCY_BUDGET_OVERRIDE_MS[routeName];
|
|
807
|
+
}
|
|
808
|
+
const type = ROUTE_TYPE_BY_NAME[routeName];
|
|
809
|
+
return type ? latencyBudgetMsForRouteType(type) : WIDEST_ROUTE_LATENCY_BUDGET_MS;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/lib/gatewaySeamStore.ts
|
|
813
|
+
var AUDIT_BUFFER_SIZE = 50;
|
|
814
|
+
var auditBufferByWorkspace = /* @__PURE__ */ new Map();
|
|
815
|
+
var nextAuditSeq = 0;
|
|
816
|
+
function getAuditLog(scopes) {
|
|
817
|
+
const merged = [];
|
|
818
|
+
for (const scope of new Set(scopes)) {
|
|
819
|
+
const bucket = auditBufferByWorkspace.get(scope);
|
|
820
|
+
if (bucket) merged.push(...bucket);
|
|
821
|
+
}
|
|
822
|
+
return merged.sort((a, b) => a.seq - b.seq);
|
|
823
|
+
}
|
|
824
|
+
function emptyByRoute() {
|
|
825
|
+
return /* @__PURE__ */ Object.create(null);
|
|
826
|
+
}
|
|
827
|
+
function emptySeamCounters() {
|
|
828
|
+
return { calls: 0, errors: 0, timeouts: 0, totalDurationMs: 0, maxDurationMs: 0, byRoute: emptyByRoute() };
|
|
829
|
+
}
|
|
830
|
+
var MAX_SEAM_WORKSPACES = 200;
|
|
831
|
+
var seamCountersByWorkspace = /* @__PURE__ */ new Map();
|
|
832
|
+
function touchBoundedWorkspaceMap(map, workspace, create) {
|
|
833
|
+
const existing = map.get(workspace);
|
|
834
|
+
if (existing !== void 0) {
|
|
835
|
+
map.delete(workspace);
|
|
836
|
+
map.set(workspace, existing);
|
|
837
|
+
return existing;
|
|
838
|
+
}
|
|
839
|
+
if (map.size >= MAX_SEAM_WORKSPACES) {
|
|
840
|
+
const leastRecentlyRecorded = map.keys().next().value;
|
|
841
|
+
if (leastRecentlyRecorded !== void 0) map.delete(leastRecentlyRecorded);
|
|
842
|
+
}
|
|
843
|
+
const created = create();
|
|
844
|
+
map.set(workspace, created);
|
|
845
|
+
return created;
|
|
846
|
+
}
|
|
847
|
+
function getOrCreateWorkspaceCounters(workspace) {
|
|
848
|
+
return touchBoundedWorkspaceMap(seamCountersByWorkspace, workspace, emptySeamCounters);
|
|
849
|
+
}
|
|
850
|
+
function getOrCreateWorkspaceAuditBuffer(workspace) {
|
|
851
|
+
return touchBoundedWorkspaceMap(auditBufferByWorkspace, workspace, () => []);
|
|
852
|
+
}
|
|
853
|
+
function getGatewaySeamCounters(workspace) {
|
|
854
|
+
const counters = seamCountersByWorkspace.get(workspace);
|
|
855
|
+
if (!counters) return emptySeamCounters();
|
|
856
|
+
return {
|
|
857
|
+
calls: counters.calls,
|
|
858
|
+
errors: counters.errors,
|
|
859
|
+
timeouts: counters.timeouts,
|
|
860
|
+
totalDurationMs: counters.totalDurationMs,
|
|
861
|
+
maxDurationMs: counters.maxDurationMs,
|
|
862
|
+
// Copied onto a null prototype, not left as `Object.fromEntries`' plain object — the
|
|
863
|
+
// snapshot is indexed by route name downstream (`getMergedGatewaySeamCounters`) and would
|
|
864
|
+
// reintroduce the inherited-member collision `emptySeamCounters` documents.
|
|
865
|
+
byRoute: Object.assign(emptyByRoute(), Object.fromEntries(Object.entries(counters.byRoute).map(([k, v]) => [k, { ...v }])))
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function getMergedGatewaySeamCounters(scopes) {
|
|
869
|
+
const merged = emptySeamCounters();
|
|
870
|
+
for (const scope of new Set(scopes)) {
|
|
871
|
+
const part = getGatewaySeamCounters(scope);
|
|
872
|
+
merged.calls += part.calls;
|
|
873
|
+
merged.errors += part.errors;
|
|
874
|
+
merged.timeouts += part.timeouts;
|
|
875
|
+
merged.totalDurationMs += part.totalDurationMs;
|
|
876
|
+
merged.maxDurationMs = Math.max(merged.maxDurationMs, part.maxDurationMs);
|
|
877
|
+
for (const [route, r] of Object.entries(part.byRoute)) {
|
|
878
|
+
const into = merged.byRoute[route] ??= { calls: 0, errors: 0, timeouts: 0, totalDurationMs: 0, maxDurationMs: 0 };
|
|
879
|
+
into.calls += r.calls;
|
|
880
|
+
into.errors += r.errors;
|
|
881
|
+
into.timeouts += r.timeouts;
|
|
882
|
+
into.totalDurationMs += r.totalDurationMs;
|
|
883
|
+
into.maxDurationMs = Math.max(into.maxDurationMs, r.maxDurationMs);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return merged;
|
|
887
|
+
}
|
|
888
|
+
function appendAuditEntry(entry) {
|
|
889
|
+
const stored = { ...entry, seq: nextAuditSeq++ };
|
|
890
|
+
const bucket = getOrCreateWorkspaceAuditBuffer(stored.workspace);
|
|
891
|
+
bucket.push(stored);
|
|
892
|
+
if (bucket.length > AUDIT_BUFFER_SIZE) bucket.shift();
|
|
893
|
+
}
|
|
894
|
+
function recordSeamCounters(workspace, fn, status, durationMs, timedOut) {
|
|
895
|
+
const seamCounters = getOrCreateWorkspaceCounters(workspace);
|
|
896
|
+
const route = seamCounters.byRoute[fn] ??= {
|
|
897
|
+
calls: 0,
|
|
898
|
+
errors: 0,
|
|
899
|
+
timeouts: 0,
|
|
900
|
+
totalDurationMs: 0,
|
|
901
|
+
maxDurationMs: 0
|
|
902
|
+
};
|
|
903
|
+
seamCounters.calls += 1;
|
|
904
|
+
seamCounters.totalDurationMs += durationMs;
|
|
905
|
+
if (durationMs > seamCounters.maxDurationMs) seamCounters.maxDurationMs = durationMs;
|
|
906
|
+
route.calls += 1;
|
|
907
|
+
route.totalDurationMs += durationMs;
|
|
908
|
+
if (durationMs > route.maxDurationMs) route.maxDurationMs = durationMs;
|
|
909
|
+
if (status === "error") {
|
|
910
|
+
seamCounters.errors += 1;
|
|
911
|
+
route.errors += 1;
|
|
912
|
+
if (timedOut) {
|
|
913
|
+
seamCounters.timeouts += 1;
|
|
914
|
+
route.timeouts += 1;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function formatGatewaySeamSummary(seam) {
|
|
919
|
+
const lines = [
|
|
920
|
+
"\n\n---\n\n# Gateway seam (process lifetime)\n",
|
|
921
|
+
`Calls: ${seam.calls} \u2014 errors: ${seam.errors}, of which timeouts: ${seam.timeouts}`
|
|
922
|
+
];
|
|
923
|
+
if (seam.calls > 0) {
|
|
924
|
+
lines.push(`Mean: ${Math.round(seam.totalDurationMs / seam.calls)}ms \u2014 slowest: ${seam.maxDurationMs}ms`);
|
|
925
|
+
}
|
|
926
|
+
if (seam.timeouts > 0) {
|
|
927
|
+
lines.push(`\u26A0 ${seam.timeouts} call(s) hit their latency budget \u2014 those outcomes are unknown, not failed.`);
|
|
928
|
+
}
|
|
929
|
+
return lines.join("\n");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/gatewaySeam.ts
|
|
933
|
+
var KernelCallError = class extends Error {
|
|
934
|
+
status;
|
|
935
|
+
code;
|
|
936
|
+
/** WP-316 S1a: Structured commit validation — required field keys missing from entry.data. */
|
|
937
|
+
missingRequiredFields;
|
|
938
|
+
/** WP-316 S1a: Structured commit validation — field-level data errors. */
|
|
939
|
+
fieldErrors;
|
|
940
|
+
/**
|
|
941
|
+
* WP-465 slice ⑤: structured diagnostics carried by an `ok:false` kernel envelope
|
|
942
|
+
* (e.g. `coherencyRefusals`, `blockers`). The gateway forwards these verbatim at
|
|
943
|
+
* HTTP 200; without preserving them here a refused/blocked envelope would collapse
|
|
944
|
+
* into a bare code+message and the caller could not surface the per-offender routes.
|
|
945
|
+
*/
|
|
946
|
+
diagnostics;
|
|
947
|
+
constructor(message, status, code, missingRequiredFields, fieldErrors, diagnostics) {
|
|
948
|
+
super(message);
|
|
949
|
+
this.name = "KernelCallError";
|
|
950
|
+
this.status = status;
|
|
951
|
+
this.code = code;
|
|
952
|
+
this.missingRequiredFields = missingRequiredFields;
|
|
953
|
+
this.fieldErrors = fieldErrors;
|
|
954
|
+
this.diagnostics = diagnostics;
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
var GatewayTimeoutError = class extends Error {
|
|
958
|
+
/** Gateway route name that was aborted. */
|
|
959
|
+
fn;
|
|
960
|
+
/** The declared budget, in ms, that this route was given (see routeLatencyBudget). */
|
|
961
|
+
budgetMs;
|
|
962
|
+
/** Wall-clock ms actually spent before the abort. */
|
|
963
|
+
elapsedMs;
|
|
964
|
+
/**
|
|
965
|
+
* True when the aborted call could have landed a write server-side despite this client
|
|
966
|
+
* giving up. Derived by `mayHaveLandedOnTimeout` from the route contract's own function
|
|
967
|
+
* type (`mutation`/`action` mutate; `query` does not) AND the call's own arguments (a
|
|
968
|
+
* `preview: true` dry run writes nothing) — never guessed from the name.
|
|
969
|
+
*/
|
|
970
|
+
mayHaveLanded;
|
|
971
|
+
constructor(fn, budgetMs, elapsedMs, mayHaveLanded) {
|
|
972
|
+
super(
|
|
973
|
+
`MCP call "${fn}" exceeded its ${budgetMs}ms latency budget (waited ${elapsedMs}ms).` + (mayHaveLanded ? " This route writes, so the server may have completed it \u2014 verify before retrying." : "")
|
|
974
|
+
);
|
|
975
|
+
this.name = "GatewayTimeoutError";
|
|
976
|
+
this.fn = fn;
|
|
977
|
+
this.budgetMs = budgetMs;
|
|
978
|
+
this.elapsedMs = elapsedMs;
|
|
979
|
+
this.mayHaveLanded = mayHaveLanded;
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
function routeMayMutate(fn) {
|
|
983
|
+
const type = ROUTE_TYPE_BY_NAME[fn];
|
|
984
|
+
return type === void 0 || type !== "query";
|
|
985
|
+
}
|
|
986
|
+
function isDryRunCall(args) {
|
|
987
|
+
return args?.preview === true;
|
|
988
|
+
}
|
|
989
|
+
function mayHaveLandedOnTimeout(fn, args) {
|
|
990
|
+
return routeMayMutate(fn) && !isDryRunCall(args);
|
|
991
|
+
}
|
|
992
|
+
function throwClassifiedGatewayFailure(err, fn, args, budgetMs, elapsedMs, phase, record) {
|
|
993
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
994
|
+
const timeoutErr = new GatewayTimeoutError(fn, budgetMs, elapsedMs, mayHaveLandedOnTimeout(fn, args));
|
|
995
|
+
record(timeoutErr.message, true);
|
|
996
|
+
throw timeoutErr;
|
|
997
|
+
}
|
|
998
|
+
const detail = err?.message ?? String(err);
|
|
999
|
+
record(phase === "network" ? detail : `${phase}: ${detail}`, false);
|
|
1000
|
+
throw new Error(`MCP call "${fn}" ${phase} error: ${detail}`);
|
|
1001
|
+
}
|
|
1002
|
+
function emptyBodyOrThrowClassified(err, fn, args, budgetMs, elapsedMs, statusOk, record) {
|
|
1003
|
+
if (statusOk) throwClassifiedGatewayFailure(err, fn, args, budgetMs, elapsedMs, "response body", record);
|
|
1004
|
+
return {};
|
|
1005
|
+
}
|
|
1006
|
+
function shouldLogAudit(status) {
|
|
1007
|
+
return status === "error" || process.env.MCP_DEBUG === "1";
|
|
1008
|
+
}
|
|
1009
|
+
function recordGatewayCall(params) {
|
|
1010
|
+
const { fn, status, durationMs, workspace, errorMsg, toolContext, budgetMs, timedOut } = params;
|
|
1011
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1012
|
+
const entry = { ts, fn, workspace, status, durationMs };
|
|
1013
|
+
if (errorMsg) entry.error = errorMsg;
|
|
1014
|
+
if (toolContext) entry.toolContext = toolContext;
|
|
1015
|
+
if (budgetMs !== void 0) entry.budgetMs = budgetMs;
|
|
1016
|
+
if (timedOut) entry.timedOut = true;
|
|
1017
|
+
appendAuditEntry(entry);
|
|
1018
|
+
recordSeamCounters(workspace, fn, status, durationMs, timedOut === true);
|
|
1019
|
+
trackToolCall(fn, status, durationMs, workspace, errorMsg);
|
|
1020
|
+
if (!shouldLogAudit(status)) return;
|
|
1021
|
+
const base = `[MCP-AUDIT] ${ts} fn=${fn} workspace=${workspace} status=${status} duration=${durationMs}ms${budgetMs !== void 0 ? ` budget=${budgetMs}ms` : ""}${timedOut ? " timedOut=true" : ""}`;
|
|
1022
|
+
process.stderr.write(
|
|
1023
|
+
status === "error" && errorMsg ? `${base} error=${JSON.stringify(errorMsg)}
|
|
1024
|
+
` : `${base}
|
|
1025
|
+
`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/client.ts
|
|
1030
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
1031
|
+
|
|
1032
|
+
// src/lib/conversation.ts
|
|
1033
|
+
import { createHash as createHash2 } from "crypto";
|
|
1034
|
+
var MAX_RAW_LEN = 128;
|
|
1035
|
+
var SAFE_CHARS_RE = /^[A-Za-z0-9._-]+$/;
|
|
1036
|
+
function normalizeConversationId(raw) {
|
|
1037
|
+
if (raw.length > MAX_RAW_LEN || !SAFE_CHARS_RE.test(raw)) {
|
|
1038
|
+
return createHash2("sha256").update(raw).digest("hex");
|
|
1039
|
+
}
|
|
1040
|
+
return raw;
|
|
1041
|
+
}
|
|
1042
|
+
function pickRawConversationId() {
|
|
1043
|
+
const candidates = [
|
|
1044
|
+
process.env.PB_CONVERSATION_ID,
|
|
1045
|
+
process.env.CLAUDE_CODE_SESSION_ID,
|
|
1046
|
+
process.env.CURSOR_CONVERSATION_ID,
|
|
1047
|
+
process.env.CODEX_TUI_SESSION_LOG_PATH
|
|
1048
|
+
];
|
|
1049
|
+
for (const candidate of candidates) {
|
|
1050
|
+
if (candidate && candidate.trim().length > 0) return candidate.trim();
|
|
1051
|
+
}
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
function resolveConversationId() {
|
|
1055
|
+
const raw = pickRawConversationId();
|
|
1056
|
+
if (!raw) return null;
|
|
1057
|
+
return normalizeConversationId(raw);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/lib/toolActionCounts.ts
|
|
1061
|
+
var toolActionCounts = /* @__PURE__ */ new Map();
|
|
1062
|
+
function actionCountKey(tool, action) {
|
|
1063
|
+
return action ? `${tool}:${action}` : tool;
|
|
1064
|
+
}
|
|
1065
|
+
function recordToolAction(tool, action) {
|
|
1066
|
+
const key = actionCountKey(tool, action);
|
|
1067
|
+
toolActionCounts.set(key, (toolActionCounts.get(key) ?? 0) + 1);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/lib/deploymentUrlResolver.ts
|
|
1071
|
+
function parseFallbackUrls(raw) {
|
|
1072
|
+
if (!raw) return [];
|
|
1073
|
+
return raw.split(",").map((u) => u.trim()).filter(Boolean);
|
|
1074
|
+
}
|
|
1075
|
+
async function probeDeploymentCandidates(candidates, apiKey) {
|
|
1076
|
+
for (const candidate of candidates) {
|
|
1077
|
+
try {
|
|
1078
|
+
const probeRes = await fetch(`${candidate}/api/key-check`, {
|
|
1079
|
+
method: "POST",
|
|
1080
|
+
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1081
|
+
signal: AbortSignal.timeout(3e3)
|
|
1082
|
+
});
|
|
1083
|
+
if (probeRes.ok) {
|
|
1084
|
+
const data = await probeRes.json();
|
|
1085
|
+
if (data.ok) return candidate;
|
|
1086
|
+
}
|
|
1087
|
+
} catch {
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/client.ts
|
|
1094
|
+
var _conversationId;
|
|
1095
|
+
function getConversationId() {
|
|
1096
|
+
const mcpSid = getRequestMcpSessionId();
|
|
1097
|
+
if (mcpSid) return `mcp:${mcpSid}`;
|
|
1098
|
+
if (getRequestApiKey()) return null;
|
|
1099
|
+
if (_conversationId === void 0) _conversationId = resolveConversationId();
|
|
1100
|
+
return _conversationId;
|
|
1101
|
+
}
|
|
1102
|
+
var toolContextStore = new AsyncLocalStorage2();
|
|
1103
|
+
function runWithToolContext(ctx, fn) {
|
|
1104
|
+
recordToolAction(ctx.tool, ctx.action);
|
|
1105
|
+
trackCompoundToolAction(ctx.tool, ctx.action, state().workspaceId ?? "unresolved");
|
|
1106
|
+
return toolContextStore.run(ctx, fn);
|
|
1107
|
+
}
|
|
1108
|
+
function getToolContext() {
|
|
1109
|
+
return toolContextStore.getStore() ?? null;
|
|
1110
|
+
}
|
|
1111
|
+
var DEFAULT_CLOUD_URL = "https://gateway.productbrain.io";
|
|
1112
|
+
var CACHE_TTL_MS = 6e4;
|
|
1113
|
+
var CACHEABLE_FNS = [
|
|
1114
|
+
"chain.getOrientEntries",
|
|
1115
|
+
"chain.gatherContext",
|
|
1116
|
+
"chain.graphGatherContext",
|
|
1117
|
+
"chain.taskAwareGatherContext",
|
|
1118
|
+
"chain.journeyAwareGatherContext",
|
|
1119
|
+
"chain.assembleBuildContext"
|
|
1120
|
+
];
|
|
1121
|
+
function isCacheable(fn) {
|
|
1122
|
+
return CACHEABLE_FNS.includes(fn);
|
|
1123
|
+
}
|
|
1124
|
+
var READ_PATTERN = /^(chain\.(get|list|search|batchGet|gather|graph|task|journey|assemble|workspace|score|absence|evaluate|shapeAdvisories|showShapeAdvisory|shapeAdvisorySummary)|chainwork\.(get|list|score)|maps\.(get|list)|gitchain\.(get|list|diff|history|runGate))/i;
|
|
1125
|
+
function isWrite(fn) {
|
|
1126
|
+
if (fn.startsWith("agent.")) return false;
|
|
1127
|
+
return !READ_PATTERN.test(fn);
|
|
1128
|
+
}
|
|
1129
|
+
var readCache = /* @__PURE__ */ new Map();
|
|
1130
|
+
function cacheKey(fn, args) {
|
|
1131
|
+
return `${fn}:${JSON.stringify(args)}`;
|
|
1132
|
+
}
|
|
1133
|
+
function getCached(fn, args) {
|
|
1134
|
+
if (!isCacheable(fn)) return void 0;
|
|
1135
|
+
const key = cacheKey(fn, args);
|
|
1136
|
+
const entry = readCache.get(key);
|
|
1137
|
+
if (!entry || Date.now() > entry.expiresAt) {
|
|
1138
|
+
if (entry) readCache.delete(key);
|
|
1139
|
+
return void 0;
|
|
1140
|
+
}
|
|
1141
|
+
return entry.data;
|
|
1142
|
+
}
|
|
1143
|
+
function setCached(fn, args, data) {
|
|
1144
|
+
if (!isCacheable(fn)) return;
|
|
1145
|
+
const key = cacheKey(fn, args);
|
|
1146
|
+
readCache.set(key, { data, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
1147
|
+
}
|
|
1148
|
+
function invalidateReadCache() {
|
|
1149
|
+
readCache.clear();
|
|
1150
|
+
}
|
|
1151
|
+
var _stdioState = {
|
|
1152
|
+
workspaceId: null,
|
|
1153
|
+
workspaceSlug: null,
|
|
1154
|
+
workspaceName: null,
|
|
1155
|
+
workspaceCreatedAt: null,
|
|
1156
|
+
workspaceGovernanceMode: null,
|
|
1157
|
+
agentSessionId: null,
|
|
1158
|
+
apiKeyId: null,
|
|
1159
|
+
apiKeyScope: "readwrite",
|
|
1160
|
+
sessionOriented: false,
|
|
1161
|
+
sessionClosed: false,
|
|
1162
|
+
lastAccess: 0,
|
|
1163
|
+
deploymentUrl: null
|
|
1164
|
+
};
|
|
1165
|
+
function state() {
|
|
1166
|
+
const reqKey = getRequestApiKey();
|
|
1167
|
+
if (reqKey) return getKeyState(reqKey);
|
|
1168
|
+
return _stdioState;
|
|
1169
|
+
}
|
|
1170
|
+
function cacheScope() {
|
|
1171
|
+
const key = getRequestApiKey();
|
|
1172
|
+
return key ? hashKey(key) : "stdio";
|
|
1173
|
+
}
|
|
1174
|
+
function getActiveApiKey() {
|
|
1175
|
+
const fromRequest = getRequestApiKey();
|
|
1176
|
+
if (fromRequest) return fromRequest;
|
|
1177
|
+
const fromEnv = process.env.PRODUCTBRAIN_API_KEY;
|
|
1178
|
+
if (!fromEnv) throw new Error("No API key available \u2014 set PRODUCTBRAIN_API_KEY or provide Bearer token");
|
|
1179
|
+
return fromEnv;
|
|
1180
|
+
}
|
|
1181
|
+
var _sessionLifecycleByStream = /* @__PURE__ */ new Map();
|
|
1182
|
+
var MAX_SESSION_STREAMS = 500;
|
|
1183
|
+
function sessionLifecycle() {
|
|
1184
|
+
const mcpSid = getRequestMcpSessionId();
|
|
1185
|
+
if (!mcpSid) return state();
|
|
1186
|
+
const key = `${cacheScope()}:${mcpSid}`;
|
|
1187
|
+
let lc = _sessionLifecycleByStream.get(key);
|
|
1188
|
+
if (!lc) {
|
|
1189
|
+
if (_sessionLifecycleByStream.size >= MAX_SESSION_STREAMS) {
|
|
1190
|
+
const oldest = _sessionLifecycleByStream.keys().next().value;
|
|
1191
|
+
if (oldest !== void 0) _sessionLifecycleByStream.delete(oldest);
|
|
1192
|
+
}
|
|
1193
|
+
lc = { agentSessionId: null, sessionOriented: false, sessionClosed: false };
|
|
1194
|
+
_sessionLifecycleByStream.set(key, lc);
|
|
1195
|
+
}
|
|
1196
|
+
return lc;
|
|
1197
|
+
}
|
|
1198
|
+
function getAgentSessionId() {
|
|
1199
|
+
return sessionLifecycle().agentSessionId;
|
|
1200
|
+
}
|
|
1201
|
+
function isSessionOriented() {
|
|
1202
|
+
return sessionLifecycle().sessionOriented;
|
|
1203
|
+
}
|
|
1204
|
+
function setSessionOriented(value) {
|
|
1205
|
+
sessionLifecycle().sessionOriented = value;
|
|
1206
|
+
}
|
|
1207
|
+
function getApiKeyScope() {
|
|
1208
|
+
return state().apiKeyScope;
|
|
1209
|
+
}
|
|
1210
|
+
async function startAgentSession() {
|
|
1211
|
+
const workspaceId = await getWorkspaceId();
|
|
1212
|
+
const s = state();
|
|
1213
|
+
if (!s.apiKeyId) {
|
|
1214
|
+
throw new Error("Cannot start session: API key ID not resolved. Ensure workspace resolution completed.");
|
|
1215
|
+
}
|
|
1216
|
+
const result = await kernelCall("agent.startSession", {
|
|
1217
|
+
workspaceId,
|
|
1218
|
+
apiKeyId: s.apiKeyId,
|
|
1219
|
+
clientKind: "mcp",
|
|
1220
|
+
// WP-479 E2: resolved once at process startup and held — see getConversationId() above.
|
|
1221
|
+
conversationId: getConversationId() ?? void 0
|
|
1222
|
+
});
|
|
1223
|
+
const lc = sessionLifecycle();
|
|
1224
|
+
if (lc.agentSessionId) {
|
|
1225
|
+
resetTouchThrottle(lc.agentSessionId);
|
|
1226
|
+
}
|
|
1227
|
+
lc.agentSessionId = result.sessionId;
|
|
1228
|
+
s.apiKeyScope = result.toolsScope;
|
|
1229
|
+
lc.sessionOriented = false;
|
|
1230
|
+
lc.sessionClosed = false;
|
|
1231
|
+
resetTouchThrottle(result.sessionId);
|
|
1232
|
+
return result;
|
|
1233
|
+
}
|
|
1234
|
+
async function closeAgentSession() {
|
|
1235
|
+
const lc = sessionLifecycle();
|
|
1236
|
+
if (!lc.agentSessionId) return;
|
|
1237
|
+
const sessionId = lc.agentSessionId;
|
|
1238
|
+
try {
|
|
1239
|
+
await kernelCall("agent.closeSession", {
|
|
1240
|
+
sessionId,
|
|
1241
|
+
status: "closed"
|
|
1242
|
+
});
|
|
1243
|
+
} finally {
|
|
1244
|
+
resetTouchThrottle(sessionId);
|
|
1245
|
+
lc.sessionClosed = true;
|
|
1246
|
+
lc.agentSessionId = null;
|
|
1247
|
+
lc.sessionOriented = false;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function orphanAgentSession() {
|
|
1251
|
+
const lc = sessionLifecycle();
|
|
1252
|
+
if (!lc.agentSessionId) return;
|
|
1253
|
+
const sessionId = lc.agentSessionId;
|
|
1254
|
+
try {
|
|
1255
|
+
await kernelCall("agent.closeSession", {
|
|
1256
|
+
sessionId,
|
|
1257
|
+
status: "orphaned"
|
|
1258
|
+
});
|
|
1259
|
+
} catch {
|
|
1260
|
+
} finally {
|
|
1261
|
+
resetTouchThrottle(sessionId);
|
|
1262
|
+
lc.agentSessionId = null;
|
|
1263
|
+
lc.sessionOriented = false;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
var _lastTouchAtBySession = /* @__PURE__ */ new Map();
|
|
1267
|
+
var TOUCH_THROTTLE_MS = 5e3;
|
|
1268
|
+
function touchSessionActivity() {
|
|
1269
|
+
const sessionId = sessionLifecycle().agentSessionId;
|
|
1270
|
+
if (!sessionId) return;
|
|
1271
|
+
const now = Date.now();
|
|
1272
|
+
const lastTouchAt = _lastTouchAtBySession.get(sessionId) ?? 0;
|
|
1273
|
+
if (now - lastTouchAt < TOUCH_THROTTLE_MS) return;
|
|
1274
|
+
_lastTouchAtBySession.set(sessionId, now);
|
|
1275
|
+
kernelCall("agent.touchSession", {
|
|
1276
|
+
sessionId
|
|
1277
|
+
}).catch(() => {
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
function resetTouchThrottle(sessionId) {
|
|
1281
|
+
if (sessionId) {
|
|
1282
|
+
_lastTouchAtBySession.delete(sessionId);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
_lastTouchAtBySession.clear();
|
|
1286
|
+
}
|
|
1287
|
+
async function recordSessionActivity(activity) {
|
|
1288
|
+
const sessionId = sessionLifecycle().agentSessionId;
|
|
1289
|
+
if (!sessionId) return;
|
|
1290
|
+
try {
|
|
1291
|
+
await kernelCall("agent.recordActivity", {
|
|
1292
|
+
sessionId,
|
|
1293
|
+
...activity
|
|
1294
|
+
});
|
|
1295
|
+
} catch {
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function bootstrap() {
|
|
1299
|
+
const explicit = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL;
|
|
1300
|
+
process.env.CONVEX_SITE_URL ??= process.env.PRODUCTBRAIN_URL ?? DEFAULT_CLOUD_URL;
|
|
1301
|
+
warnOnProdFallthrough(process.env.CONVEX_SITE_URL, { explicit: explicit != null });
|
|
1302
|
+
const pbKey = process.env.PRODUCTBRAIN_API_KEY;
|
|
1303
|
+
if (!pbKey?.startsWith("pb_sk_")) {
|
|
1304
|
+
process.stderr.write(
|
|
1305
|
+
"[MCP] Warning: PRODUCTBRAIN_API_KEY is not set or invalid. Tool calls will fail until a valid key is provided.\n"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
function bootstrapHttp() {
|
|
1310
|
+
const explicit = process.env.CONVEX_SITE_URL ?? process.env.PRODUCTBRAIN_URL;
|
|
1311
|
+
process.env.CONVEX_SITE_URL ??= process.env.PRODUCTBRAIN_URL ?? DEFAULT_CLOUD_URL;
|
|
1312
|
+
warnOnProdFallthrough(process.env.CONVEX_SITE_URL, { explicit: explicit != null });
|
|
1313
|
+
}
|
|
1314
|
+
async function resolveDeploymentUrl() {
|
|
1315
|
+
const s = state();
|
|
1316
|
+
if (s.deploymentUrl) return s.deploymentUrl;
|
|
1317
|
+
const primaryUrl = (process.env.CONVEX_SITE_URL ?? DEFAULT_CLOUD_URL).replace(/\/$/, "");
|
|
1318
|
+
const fallbacks = parseFallbackUrls(process.env.CONVEX_FALLBACK_URLS);
|
|
1319
|
+
if (fallbacks.length === 0) {
|
|
1320
|
+
return primaryUrl;
|
|
1321
|
+
}
|
|
1322
|
+
const candidates = [primaryUrl, ...fallbacks.map((u) => u.replace(/\/$/, ""))];
|
|
1323
|
+
let apiKey;
|
|
1324
|
+
try {
|
|
1325
|
+
apiKey = getActiveApiKey();
|
|
1326
|
+
} catch {
|
|
1327
|
+
return primaryUrl;
|
|
1328
|
+
}
|
|
1329
|
+
const found = await probeDeploymentCandidates(candidates, apiKey);
|
|
1330
|
+
if (found) {
|
|
1331
|
+
s.deploymentUrl = found;
|
|
1332
|
+
return found;
|
|
1333
|
+
}
|
|
1334
|
+
return candidates[0];
|
|
1335
|
+
}
|
|
1336
|
+
function audit(fn, status, durationMs, errorMsg, meta) {
|
|
1337
|
+
recordGatewayCall({
|
|
1338
|
+
fn,
|
|
1339
|
+
status,
|
|
1340
|
+
durationMs,
|
|
1341
|
+
errorMsg,
|
|
1342
|
+
workspace: state().workspaceId ?? cacheScope(),
|
|
1343
|
+
toolContext: getToolContext(),
|
|
1344
|
+
budgetMs: meta?.budgetMs,
|
|
1345
|
+
timedOut: meta?.timedOut
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
var TOUCH_EXCLUDED = /* @__PURE__ */ new Set([
|
|
1349
|
+
"agent.touchSession",
|
|
1350
|
+
"agent.startSession",
|
|
1351
|
+
"agent.markOriented",
|
|
1352
|
+
"agent.recordActivity",
|
|
1353
|
+
"agent.recordWrapup",
|
|
1354
|
+
"agent.closeSession",
|
|
1355
|
+
// WP-376 α.3: orient byte report is itself a heartbeat-equivalent observability
|
|
1356
|
+
// write that already patches the same agentSessions row. A follow-up touchSession
|
|
1357
|
+
// would create a redundant second write per orient call (DEC-50 OCC anti-pattern).
|
|
1358
|
+
"agent.reportOrientMetric"
|
|
1359
|
+
]);
|
|
1360
|
+
var MCP_TELEMETRY_SOURCE = "mcp";
|
|
1361
|
+
async function callGateway(fn, args) {
|
|
1362
|
+
const siteUrl = await resolveDeploymentUrl();
|
|
1363
|
+
const apiKey = getActiveApiKey();
|
|
1364
|
+
const budgetMs = latencyBudgetMsForRoute(fn);
|
|
1365
|
+
const start = Date.now();
|
|
1366
|
+
let res;
|
|
1367
|
+
try {
|
|
1368
|
+
res = await fetch(`${siteUrl}/api/aki`, {
|
|
1369
|
+
method: "POST",
|
|
1370
|
+
signal: AbortSignal.timeout(budgetMs),
|
|
1371
|
+
headers: {
|
|
1372
|
+
"Content-Type": "application/json",
|
|
1373
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1374
|
+
// DEC-1207: attribute this connector for the gateway's context.served seam. MCP is
|
|
1375
|
+
// the dominant agent surface (INS-1706); without this header it was emitting nothing.
|
|
1376
|
+
"x-pb-source": MCP_TELEMETRY_SOURCE
|
|
1377
|
+
},
|
|
1378
|
+
body: JSON.stringify({ fn, args })
|
|
1379
|
+
});
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
throwClassifiedGatewayFailure(
|
|
1382
|
+
err,
|
|
1383
|
+
fn,
|
|
1384
|
+
args,
|
|
1385
|
+
budgetMs,
|
|
1386
|
+
Date.now() - start,
|
|
1387
|
+
"network",
|
|
1388
|
+
(m, t) => audit(fn, "error", Date.now() - start, m, { budgetMs, timedOut: t })
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
let json;
|
|
1392
|
+
try {
|
|
1393
|
+
json = await res.json();
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
json = emptyBodyOrThrowClassified(
|
|
1396
|
+
err,
|
|
1397
|
+
fn,
|
|
1398
|
+
args,
|
|
1399
|
+
budgetMs,
|
|
1400
|
+
Date.now() - start,
|
|
1401
|
+
res.ok,
|
|
1402
|
+
(m, t) => audit(fn, "error", Date.now() - start, m, { budgetMs, timedOut: t })
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
if (!res.ok || json.ok === false) {
|
|
1406
|
+
const errJson = json;
|
|
1407
|
+
const msg = errJson.error ?? errJson.message ?? "unknown error";
|
|
1408
|
+
audit(fn, "error", Date.now() - start, errJson.code ? `${msg} [${errJson.code}]` : msg, { budgetMs });
|
|
1409
|
+
throw new KernelCallError(
|
|
1410
|
+
`MCP call "${fn}" failed (${res.status}): ${msg}`,
|
|
1411
|
+
res.status,
|
|
1412
|
+
errJson.code,
|
|
1413
|
+
Array.isArray(errJson.missingRequiredFields) ? errJson.missingRequiredFields : void 0,
|
|
1414
|
+
Array.isArray(errJson.fieldErrors) ? errJson.fieldErrors : void 0,
|
|
1415
|
+
errJson.diagnostics && typeof errJson.diagnostics === "object" ? errJson.diagnostics : void 0
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
audit(fn, "ok", Date.now() - start, void 0, { budgetMs });
|
|
1419
|
+
const { data, summary, next, _meta } = json;
|
|
1420
|
+
return {
|
|
1421
|
+
data,
|
|
1422
|
+
summary: summary || fn,
|
|
1423
|
+
next,
|
|
1424
|
+
_meta
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
async function kernelCall(fn, args = {}) {
|
|
1428
|
+
const cached = getCached(fn, args);
|
|
1429
|
+
if (cached !== void 0) {
|
|
1430
|
+
return cached;
|
|
1431
|
+
}
|
|
1432
|
+
const { data } = await callGateway(fn, args);
|
|
1433
|
+
if (isWrite(fn)) {
|
|
1434
|
+
invalidateReadCache();
|
|
1435
|
+
} else {
|
|
1436
|
+
setCached(fn, args, data);
|
|
1437
|
+
}
|
|
1438
|
+
if (getAgentSessionId() && !TOUCH_EXCLUDED.has(fn)) {
|
|
1439
|
+
touchSessionActivity();
|
|
1440
|
+
}
|
|
1441
|
+
return data;
|
|
1442
|
+
}
|
|
1443
|
+
async function kernelCallEnvelope(fn, args = {}) {
|
|
1444
|
+
const { data, summary, next, _meta } = await callGateway(fn, args);
|
|
1445
|
+
if (getAgentSessionId() && !TOUCH_EXCLUDED.has(fn)) {
|
|
1446
|
+
touchSessionActivity();
|
|
1447
|
+
}
|
|
1448
|
+
return { ok: true, summary, data, next, _meta };
|
|
1449
|
+
}
|
|
1450
|
+
var resolveInFlightMap = /* @__PURE__ */ new Map();
|
|
1451
|
+
async function getWorkspaceId() {
|
|
1452
|
+
const s = state();
|
|
1453
|
+
if (s.workspaceId) return s.workspaceId;
|
|
1454
|
+
const apiKey = getActiveApiKey();
|
|
1455
|
+
const existing = resolveInFlightMap.get(apiKey);
|
|
1456
|
+
if (existing) return existing;
|
|
1457
|
+
const promise = resolveWorkspaceWithRetry().finally(() => resolveInFlightMap.delete(apiKey));
|
|
1458
|
+
resolveInFlightMap.set(apiKey, promise);
|
|
1459
|
+
return promise;
|
|
1460
|
+
}
|
|
1461
|
+
async function resolveWorkspaceWithRetry(maxRetries = 2) {
|
|
1462
|
+
let lastError = null;
|
|
1463
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1464
|
+
try {
|
|
1465
|
+
const workspace = await kernelCall("resolveWorkspace", {});
|
|
1466
|
+
if (!workspace) {
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
`API key is valid but no workspace is associated. Run \`npx ${MCP_NPX_PACKAGE} setup\` or regenerate your key.`
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
const s = state();
|
|
1472
|
+
s.workspaceId = workspace._id;
|
|
1473
|
+
s.workspaceSlug = workspace.slug;
|
|
1474
|
+
s.workspaceName = workspace.name;
|
|
1475
|
+
s.workspaceCreatedAt = workspace.createdAt ?? null;
|
|
1476
|
+
s.workspaceGovernanceMode = workspace.governanceMode ?? "open";
|
|
1477
|
+
if (workspace.keyScope) s.apiKeyScope = workspace.keyScope;
|
|
1478
|
+
if (workspace.keyId) s.apiKeyId = workspace.keyId;
|
|
1479
|
+
return s.workspaceId;
|
|
1480
|
+
} catch (err) {
|
|
1481
|
+
lastError = err;
|
|
1482
|
+
const isTransient = err?.name === "GatewayTimeoutError" || /network error|fetch failed|ECONNREFUSED|ETIMEDOUT/i.test(err.message);
|
|
1483
|
+
if (!isTransient || attempt === maxRetries) break;
|
|
1484
|
+
const delay = 1e3 * (attempt + 1);
|
|
1485
|
+
process.stderr.write(
|
|
1486
|
+
`[MCP] Workspace resolution failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms...
|
|
1487
|
+
`
|
|
1488
|
+
);
|
|
1489
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
throw lastError;
|
|
1493
|
+
}
|
|
1494
|
+
async function getWorkspaceContext() {
|
|
1495
|
+
const workspaceId = await getWorkspaceId();
|
|
1496
|
+
const s = state();
|
|
1497
|
+
return {
|
|
1498
|
+
workspaceId,
|
|
1499
|
+
workspaceSlug: s.workspaceSlug ?? "unknown",
|
|
1500
|
+
workspaceName: s.workspaceName ?? "unknown",
|
|
1501
|
+
createdAt: s.workspaceCreatedAt,
|
|
1502
|
+
governanceMode: s.workspaceGovernanceMode ?? "open"
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
async function refreshWorkspaceGovernanceMode() {
|
|
1506
|
+
const workspace = await kernelCall("resolveWorkspace", {});
|
|
1507
|
+
const mode = workspace?.governanceMode ?? "open";
|
|
1508
|
+
const s = state();
|
|
1509
|
+
s.workspaceGovernanceMode = mode;
|
|
1510
|
+
return mode;
|
|
1511
|
+
}
|
|
1512
|
+
async function kernelQuery(fn, args = {}) {
|
|
1513
|
+
const workspaceId = await getWorkspaceId();
|
|
1514
|
+
return kernelCall(fn, { ...args, workspaceId });
|
|
1515
|
+
}
|
|
1516
|
+
async function kernelMutation(fn, args = {}) {
|
|
1517
|
+
const workspaceId = await getWorkspaceId();
|
|
1518
|
+
return kernelCall(fn, { ...args, workspaceId });
|
|
1519
|
+
}
|
|
1520
|
+
function requireActiveSession() {
|
|
1521
|
+
const lc = sessionLifecycle();
|
|
1522
|
+
if (!lc.agentSessionId) {
|
|
1523
|
+
throw new Error(
|
|
1524
|
+
"Active session required (SOS-iszqu7). Call `session action=start` then `orient` first."
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (lc.sessionClosed) {
|
|
1528
|
+
throw new Error(
|
|
1529
|
+
"Session has been closed (SOS-iszqu7). Start a new session with `session action=start`."
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
if (!lc.sessionOriented) {
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
"Orientation required before accessing build context (SOS-iszqu7). Call `orient` first."
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function requireWriteAccess() {
|
|
1539
|
+
const lc = sessionLifecycle();
|
|
1540
|
+
const s = state();
|
|
1541
|
+
if (!lc.agentSessionId) {
|
|
1542
|
+
throw new Error(
|
|
1543
|
+
"Agent session required for write operations. Call `session action=start` first."
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
if (lc.sessionClosed) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
"Agent session has been closed. Write tools are no longer available."
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
if (!lc.sessionOriented) {
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
"Orientation required before writing to the Chain. Call 'orient' first."
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
if (s.apiKeyScope === "read") {
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
"This API key has read-only scope. Write tools are not available."
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
function requireVendorTriageAccess() {
|
|
1563
|
+
const lc = sessionLifecycle();
|
|
1564
|
+
const s = state();
|
|
1565
|
+
if (!lc.agentSessionId) {
|
|
1566
|
+
throw new Error(
|
|
1567
|
+
"Agent session required for feedback triage. Call `session action=start` first."
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
if (lc.sessionClosed) {
|
|
1571
|
+
throw new Error(
|
|
1572
|
+
"Agent session has been closed. Feedback triage is no longer available."
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
if (s.apiKeyScope === "read") {
|
|
1576
|
+
throw new Error(
|
|
1577
|
+
"This API key has read-only scope. Feedback triage is not available."
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
async function recoverSessionState() {
|
|
1582
|
+
const s = state();
|
|
1583
|
+
if (!s.workspaceId) return;
|
|
1584
|
+
try {
|
|
1585
|
+
const session = await kernelCall("agent.getActiveSession", {
|
|
1586
|
+
workspaceId: s.workspaceId,
|
|
1587
|
+
// WP-479 E2: required disambiguation arg for the per-apiKey-quota re-scoped
|
|
1588
|
+
// getActiveSession — absent identity (null) still resolves to the keyless/legacy path
|
|
1589
|
+
// server-side.
|
|
1590
|
+
conversationId: getConversationId() ?? void 0
|
|
1591
|
+
});
|
|
1592
|
+
if (session && session.status === "active") {
|
|
1593
|
+
const lc = sessionLifecycle();
|
|
1594
|
+
lc.agentSessionId = session._id;
|
|
1595
|
+
lc.sessionOriented = session.oriented;
|
|
1596
|
+
s.apiKeyScope = session.toolsScope;
|
|
1597
|
+
lc.sessionClosed = false;
|
|
1598
|
+
}
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/prod-fallthrough.ts
|
|
1604
|
+
function warnOnProdFallthrough(resolved, opts) {
|
|
1605
|
+
if (opts.explicit) return;
|
|
1606
|
+
if (resolved.replace(/\/$/, "") !== DEFAULT_CLOUD_URL.replace(/\/$/, "")) return;
|
|
1607
|
+
process.stderr.write(
|
|
1608
|
+
`[MCP] No deployment URL configured \u2014 defaulting to the production gateway ${DEFAULT_CLOUD_URL}. Set CONVEX_SITE_URL or PRODUCTBRAIN_URL to target a different deployment.
|
|
1609
|
+
`
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export {
|
|
1614
|
+
initAnalytics,
|
|
1615
|
+
trackSessionStarted,
|
|
1616
|
+
trackSetupStarted,
|
|
1617
|
+
trackSetupCompleted,
|
|
1618
|
+
trackQualityVerdict,
|
|
1619
|
+
trackQualityCheck,
|
|
1620
|
+
trackCaptureClassifierEvaluated,
|
|
1621
|
+
trackCaptureClassifierAutoRouted,
|
|
1622
|
+
trackCaptureClassifierFallback,
|
|
1623
|
+
trackChainEntryCommitted,
|
|
1624
|
+
trackKnowledgeGap,
|
|
1625
|
+
trackCaptureQualityHints,
|
|
1626
|
+
trackCaptureRelationSuggestions,
|
|
1627
|
+
trackCollectionClassified,
|
|
1628
|
+
trackFieldGuidanceApplied,
|
|
1629
|
+
trackFieldQualityWarning,
|
|
1630
|
+
trackSessionCaptureRate,
|
|
1631
|
+
trackZeroCaptureAuditFired,
|
|
1632
|
+
trackCaptureContractMiss,
|
|
1633
|
+
trackWriteBackHintServed,
|
|
1634
|
+
trackCommitErrorByCode,
|
|
1635
|
+
trackClassifierDivergence,
|
|
1636
|
+
getPostHogClient,
|
|
1637
|
+
shutdownAnalytics,
|
|
1638
|
+
getAuditLog,
|
|
1639
|
+
getMergedGatewaySeamCounters,
|
|
1640
|
+
formatGatewaySeamSummary,
|
|
1641
|
+
KernelCallError,
|
|
1642
|
+
hashKey,
|
|
1643
|
+
runWithAuth,
|
|
1644
|
+
getRequestApiKey,
|
|
1645
|
+
getKeyState,
|
|
1646
|
+
MCP_NPX_PACKAGE,
|
|
1647
|
+
resolveClient,
|
|
1648
|
+
writeClientConfig,
|
|
1649
|
+
warnOnProdFallthrough,
|
|
1650
|
+
getConversationId,
|
|
1651
|
+
runWithToolContext,
|
|
1652
|
+
DEFAULT_CLOUD_URL,
|
|
1653
|
+
cacheScope,
|
|
1654
|
+
getAgentSessionId,
|
|
1655
|
+
isSessionOriented,
|
|
1656
|
+
setSessionOriented,
|
|
1657
|
+
getApiKeyScope,
|
|
1658
|
+
startAgentSession,
|
|
1659
|
+
closeAgentSession,
|
|
1660
|
+
orphanAgentSession,
|
|
1661
|
+
recordSessionActivity,
|
|
1662
|
+
bootstrap,
|
|
1663
|
+
bootstrapHttp,
|
|
1664
|
+
kernelCall,
|
|
1665
|
+
kernelCallEnvelope,
|
|
1666
|
+
getWorkspaceId,
|
|
1667
|
+
getWorkspaceContext,
|
|
1668
|
+
refreshWorkspaceGovernanceMode,
|
|
1669
|
+
kernelQuery,
|
|
1670
|
+
kernelMutation,
|
|
1671
|
+
requireActiveSession,
|
|
1672
|
+
requireWriteAccess,
|
|
1673
|
+
requireVendorTriageAccess,
|
|
1674
|
+
recoverSessionState
|
|
1675
|
+
};
|
|
1676
|
+
//# sourceMappingURL=chunk-DSSH6AT2.js.map
|