@usewhisper/mcp-server 2.4.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/server.js +2472 -149
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -81,6 +81,9 @@ function stableHash(input) {
|
|
|
81
81
|
}
|
|
82
82
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
83
83
|
}
|
|
84
|
+
function normalizeQuery(query) {
|
|
85
|
+
return query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
86
|
+
}
|
|
84
87
|
function randomId(prefix = "id") {
|
|
85
88
|
return `${prefix}_${stableHash(`${Date.now()}_${Math.random()}`)}`;
|
|
86
89
|
}
|
|
@@ -263,129 +266,2269 @@ var RuntimeClient = class {
|
|
|
263
266
|
return cloned;
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
|
-
const runner = this.performRequest(options).then((data) => {
|
|
267
|
-
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
268
|
-
return data;
|
|
269
|
-
}).catch((error) => {
|
|
270
|
-
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
271
|
-
throw error;
|
|
269
|
+
const runner = this.performRequest(options).then((data) => {
|
|
270
|
+
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
271
|
+
return data;
|
|
272
|
+
}).catch((error) => {
|
|
273
|
+
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
274
|
+
throw error;
|
|
275
|
+
});
|
|
276
|
+
if (dedupeKey) {
|
|
277
|
+
this.inFlight.set(dedupeKey, runner);
|
|
278
|
+
}
|
|
279
|
+
return runner;
|
|
280
|
+
}
|
|
281
|
+
async performRequest(options) {
|
|
282
|
+
const method = options.method || "GET";
|
|
283
|
+
const normalizedEndpoint = normalizeEndpoint(options.endpoint);
|
|
284
|
+
const operation = options.operation;
|
|
285
|
+
const maxAttempts = this.maxAttemptsFor(operation);
|
|
286
|
+
const timeoutMs = this.timeoutFor(operation);
|
|
287
|
+
const traceId = options.traceId || randomId("trace");
|
|
288
|
+
let lastError = null;
|
|
289
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
290
|
+
const spanId = randomId("span");
|
|
291
|
+
const startedAt = Date.now();
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
294
|
+
try {
|
|
295
|
+
const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
|
|
296
|
+
const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
|
|
297
|
+
method,
|
|
298
|
+
signal: controller.signal,
|
|
299
|
+
keepalive: method !== "GET",
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Type": "application/json",
|
|
302
|
+
Authorization: this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`,
|
|
303
|
+
...attachApiKeyHeader ? { "X-API-Key": this.apiKey.replace(/^Bearer\s+/i, "") } : {},
|
|
304
|
+
"x-trace-id": traceId,
|
|
305
|
+
"x-span-id": spanId,
|
|
306
|
+
"x-sdk-version": this.sdkVersion,
|
|
307
|
+
"x-sdk-runtime": this.runtimeName(),
|
|
308
|
+
...options.headers || {}
|
|
309
|
+
},
|
|
310
|
+
body: method === "GET" || method === "DELETE" ? void 0 : JSON.stringify(options.body || {})
|
|
311
|
+
});
|
|
312
|
+
clearTimeout(timeout);
|
|
313
|
+
let payload = null;
|
|
314
|
+
try {
|
|
315
|
+
payload = await response.json();
|
|
316
|
+
} catch {
|
|
317
|
+
payload = await response.text().catch(() => "");
|
|
318
|
+
}
|
|
319
|
+
const durationMs = Date.now() - startedAt;
|
|
320
|
+
const record = {
|
|
321
|
+
id: randomId("diag"),
|
|
322
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
323
|
+
endedAt: nowIso(),
|
|
324
|
+
traceId,
|
|
325
|
+
spanId,
|
|
326
|
+
operation,
|
|
327
|
+
method,
|
|
328
|
+
endpoint: normalizedEndpoint,
|
|
329
|
+
status: response.status,
|
|
330
|
+
durationMs,
|
|
331
|
+
success: response.ok
|
|
332
|
+
};
|
|
333
|
+
this.diagnostics.add(record);
|
|
334
|
+
if (response.ok) {
|
|
335
|
+
return {
|
|
336
|
+
data: payload,
|
|
337
|
+
status: response.status,
|
|
338
|
+
traceId
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const message = toMessage(payload, response.status, response.statusText);
|
|
342
|
+
const retryable = this.shouldRetryStatus(response.status);
|
|
343
|
+
const error = new RuntimeClientError({
|
|
344
|
+
message,
|
|
345
|
+
status: response.status,
|
|
346
|
+
retryable,
|
|
347
|
+
code: response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED",
|
|
348
|
+
details: payload,
|
|
349
|
+
traceId
|
|
350
|
+
});
|
|
351
|
+
lastError = error;
|
|
352
|
+
if (!retryable || attempt === maxAttempts - 1) {
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
clearTimeout(timeout);
|
|
357
|
+
const durationMs = Date.now() - startedAt;
|
|
358
|
+
const isAbort = isObject(error) && error.name === "AbortError";
|
|
359
|
+
const mapped = error instanceof RuntimeClientError ? error : new RuntimeClientError({
|
|
360
|
+
message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
|
|
361
|
+
retryable: this.retryPolicy.retryOnNetworkError ?? true,
|
|
362
|
+
code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
|
|
363
|
+
traceId
|
|
364
|
+
});
|
|
365
|
+
lastError = mapped;
|
|
366
|
+
this.diagnostics.add({
|
|
367
|
+
id: randomId("diag"),
|
|
368
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
369
|
+
endedAt: nowIso(),
|
|
370
|
+
traceId,
|
|
371
|
+
spanId,
|
|
372
|
+
operation,
|
|
373
|
+
method,
|
|
374
|
+
endpoint: normalizedEndpoint,
|
|
375
|
+
durationMs,
|
|
376
|
+
success: false,
|
|
377
|
+
errorCode: mapped.code,
|
|
378
|
+
errorMessage: mapped.message
|
|
379
|
+
});
|
|
380
|
+
if (!mapped.retryable || attempt === maxAttempts - 1) {
|
|
381
|
+
throw mapped;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, this.backoff(attempt)));
|
|
385
|
+
}
|
|
386
|
+
throw lastError || new RuntimeClientError({
|
|
387
|
+
message: "Request failed",
|
|
388
|
+
retryable: false,
|
|
389
|
+
code: "REQUEST_FAILED"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// ../src/sdk/core/cache.ts
|
|
395
|
+
var SearchResponseCache = class {
|
|
396
|
+
ttlMs;
|
|
397
|
+
capacity;
|
|
398
|
+
byKey = /* @__PURE__ */ new Map();
|
|
399
|
+
scopeIndex = /* @__PURE__ */ new Map();
|
|
400
|
+
constructor(ttlMs = 7e3, capacity = 500) {
|
|
401
|
+
this.ttlMs = Math.max(1e3, ttlMs);
|
|
402
|
+
this.capacity = Math.max(10, capacity);
|
|
403
|
+
}
|
|
404
|
+
makeScopeKey(project, userId, sessionId) {
|
|
405
|
+
return `${project}:${userId || "_"}:${sessionId || "_"}`;
|
|
406
|
+
}
|
|
407
|
+
makeKey(input) {
|
|
408
|
+
const normalized = {
|
|
409
|
+
project: input.project,
|
|
410
|
+
userId: input.userId || "",
|
|
411
|
+
sessionId: input.sessionId || "",
|
|
412
|
+
query: normalizeQuery(input.query),
|
|
413
|
+
topK: input.topK,
|
|
414
|
+
profile: input.profile,
|
|
415
|
+
includePending: input.includePending
|
|
416
|
+
};
|
|
417
|
+
return `search:${stableHash(JSON.stringify(normalized))}`;
|
|
418
|
+
}
|
|
419
|
+
get(key) {
|
|
420
|
+
const found = this.byKey.get(key);
|
|
421
|
+
if (!found) return null;
|
|
422
|
+
if (found.expiresAt <= Date.now()) {
|
|
423
|
+
this.deleteByKey(key);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
found.touchedAt = Date.now();
|
|
427
|
+
return found.value;
|
|
428
|
+
}
|
|
429
|
+
set(key, scopeKey, value) {
|
|
430
|
+
this.byKey.set(key, {
|
|
431
|
+
value,
|
|
432
|
+
scopeKey,
|
|
433
|
+
touchedAt: Date.now(),
|
|
434
|
+
expiresAt: Date.now() + this.ttlMs
|
|
435
|
+
});
|
|
436
|
+
if (!this.scopeIndex.has(scopeKey)) {
|
|
437
|
+
this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
|
|
438
|
+
}
|
|
439
|
+
this.scopeIndex.get(scopeKey).add(key);
|
|
440
|
+
this.evictIfNeeded();
|
|
441
|
+
}
|
|
442
|
+
invalidateScope(scopeKey) {
|
|
443
|
+
const keys = this.scopeIndex.get(scopeKey);
|
|
444
|
+
if (!keys || keys.size === 0) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
const toDelete = Array.from(keys);
|
|
448
|
+
for (const key of toDelete) {
|
|
449
|
+
this.deleteByKey(key);
|
|
450
|
+
}
|
|
451
|
+
this.scopeIndex.delete(scopeKey);
|
|
452
|
+
return toDelete.length;
|
|
453
|
+
}
|
|
454
|
+
evictIfNeeded() {
|
|
455
|
+
if (this.byKey.size <= this.capacity) return;
|
|
456
|
+
const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
|
|
457
|
+
const removeCount = this.byKey.size - this.capacity;
|
|
458
|
+
for (let i = 0; i < removeCount; i += 1) {
|
|
459
|
+
this.deleteByKey(ordered[i][0]);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
deleteByKey(key) {
|
|
463
|
+
const found = this.byKey.get(key);
|
|
464
|
+
if (!found) return;
|
|
465
|
+
this.byKey.delete(key);
|
|
466
|
+
const scopeKeys = this.scopeIndex.get(found.scopeKey);
|
|
467
|
+
if (!scopeKeys) return;
|
|
468
|
+
scopeKeys.delete(key);
|
|
469
|
+
if (scopeKeys.size === 0) {
|
|
470
|
+
this.scopeIndex.delete(found.scopeKey);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// ../src/sdk/core/queue.ts
|
|
476
|
+
var InMemoryQueueStore = class {
|
|
477
|
+
items = [];
|
|
478
|
+
async load() {
|
|
479
|
+
return [...this.items];
|
|
480
|
+
}
|
|
481
|
+
async save(items) {
|
|
482
|
+
this.items = [...items];
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
var WriteQueue = class {
|
|
486
|
+
flushHandler;
|
|
487
|
+
store;
|
|
488
|
+
maxBatchSize;
|
|
489
|
+
flushIntervalMs;
|
|
490
|
+
maxAttempts;
|
|
491
|
+
queue = [];
|
|
492
|
+
flushTimer = null;
|
|
493
|
+
flushing = false;
|
|
494
|
+
lastFlushAt;
|
|
495
|
+
lastFlushCount = 0;
|
|
496
|
+
constructor(args) {
|
|
497
|
+
this.flushHandler = args.flushHandler;
|
|
498
|
+
this.store = args.store || new InMemoryQueueStore();
|
|
499
|
+
this.maxBatchSize = Math.max(1, args.maxBatchSize ?? 50);
|
|
500
|
+
this.flushIntervalMs = Math.max(10, args.flushIntervalMs ?? 100);
|
|
501
|
+
this.maxAttempts = Math.max(1, args.maxAttempts ?? 2);
|
|
502
|
+
}
|
|
503
|
+
async start() {
|
|
504
|
+
const pending = await this.store.load();
|
|
505
|
+
if (pending.length > 0) {
|
|
506
|
+
this.queue.push(...pending);
|
|
507
|
+
}
|
|
508
|
+
if (!this.flushTimer) {
|
|
509
|
+
this.flushTimer = setInterval(() => {
|
|
510
|
+
void this.flush();
|
|
511
|
+
}, this.flushIntervalMs);
|
|
512
|
+
const timer = this.flushTimer;
|
|
513
|
+
if (typeof timer.unref === "function") {
|
|
514
|
+
timer.unref();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
this.bindProcessHooks();
|
|
518
|
+
}
|
|
519
|
+
async stop() {
|
|
520
|
+
if (this.flushTimer) {
|
|
521
|
+
clearInterval(this.flushTimer);
|
|
522
|
+
this.flushTimer = null;
|
|
523
|
+
}
|
|
524
|
+
await this.flush();
|
|
525
|
+
}
|
|
526
|
+
status() {
|
|
527
|
+
return {
|
|
528
|
+
queued: this.queue.length,
|
|
529
|
+
flushing: this.flushing,
|
|
530
|
+
lastFlushAt: this.lastFlushAt,
|
|
531
|
+
lastFlushCount: this.lastFlushCount
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
async enqueue(input) {
|
|
535
|
+
const eventId = input.eventId || this.makeEventId(input);
|
|
536
|
+
const item = {
|
|
537
|
+
...input,
|
|
538
|
+
eventId,
|
|
539
|
+
createdAt: nowIso()
|
|
540
|
+
};
|
|
541
|
+
this.queue.push(item);
|
|
542
|
+
await this.store.save(this.queue);
|
|
543
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
544
|
+
void this.flush();
|
|
545
|
+
}
|
|
546
|
+
return item;
|
|
547
|
+
}
|
|
548
|
+
async flush() {
|
|
549
|
+
if (this.flushing || this.queue.length === 0) return;
|
|
550
|
+
this.flushing = true;
|
|
551
|
+
try {
|
|
552
|
+
while (this.queue.length > 0) {
|
|
553
|
+
const batch = this.queue.slice(0, this.maxBatchSize);
|
|
554
|
+
let done = false;
|
|
555
|
+
let error = null;
|
|
556
|
+
for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
|
|
557
|
+
try {
|
|
558
|
+
await this.flushHandler(batch);
|
|
559
|
+
done = true;
|
|
560
|
+
break;
|
|
561
|
+
} catch (err) {
|
|
562
|
+
error = err;
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, (attempt + 1) * 180));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!done) {
|
|
567
|
+
throw error instanceof Error ? error : new Error("Queue flush failed");
|
|
568
|
+
}
|
|
569
|
+
this.queue.splice(0, batch.length);
|
|
570
|
+
this.lastFlushAt = nowIso();
|
|
571
|
+
this.lastFlushCount = batch.length;
|
|
572
|
+
await this.store.save(this.queue);
|
|
573
|
+
}
|
|
574
|
+
} finally {
|
|
575
|
+
this.flushing = false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
makeEventId(input) {
|
|
579
|
+
const source = JSON.stringify({
|
|
580
|
+
project: input.project,
|
|
581
|
+
userId: input.userId || "",
|
|
582
|
+
sessionId: input.sessionId || "",
|
|
583
|
+
payload: input.payload
|
|
584
|
+
});
|
|
585
|
+
return `evt_${stableHash(source)}`;
|
|
586
|
+
}
|
|
587
|
+
bindProcessHooks() {
|
|
588
|
+
if (typeof process === "undefined") return;
|
|
589
|
+
const proc = process;
|
|
590
|
+
const flushOnExit = () => {
|
|
591
|
+
void this.flush();
|
|
592
|
+
};
|
|
593
|
+
proc.once("beforeExit", flushOnExit);
|
|
594
|
+
proc.once("SIGINT", flushOnExit);
|
|
595
|
+
proc.once("SIGTERM", flushOnExit);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
function createStorageQueueStore(key = "whisper_sdk_queue") {
|
|
599
|
+
const getStorage = () => {
|
|
600
|
+
const maybeStorage = globalThis.localStorage;
|
|
601
|
+
if (!maybeStorage || typeof maybeStorage !== "object") return null;
|
|
602
|
+
const candidate = maybeStorage;
|
|
603
|
+
if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
getItem: candidate.getItem,
|
|
608
|
+
setItem: candidate.setItem
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
return {
|
|
612
|
+
async load() {
|
|
613
|
+
const storage = getStorage();
|
|
614
|
+
if (!storage) return [];
|
|
615
|
+
const raw = storage.getItem(key);
|
|
616
|
+
if (!raw) return [];
|
|
617
|
+
try {
|
|
618
|
+
const parsed = JSON.parse(raw);
|
|
619
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
620
|
+
} catch {
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
async save(items) {
|
|
625
|
+
const storage = getStorage();
|
|
626
|
+
if (!storage) return;
|
|
627
|
+
storage.setItem(key, JSON.stringify(items));
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function createFileQueueStore(filePath) {
|
|
632
|
+
return {
|
|
633
|
+
async load() {
|
|
634
|
+
if (typeof process === "undefined") return [];
|
|
635
|
+
const fs = await import("fs/promises");
|
|
636
|
+
try {
|
|
637
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
638
|
+
const parsed = JSON.parse(raw);
|
|
639
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const nodeError = error;
|
|
642
|
+
if (nodeError?.code === "ENOENT") {
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
async save(items) {
|
|
649
|
+
if (typeof process === "undefined") return;
|
|
650
|
+
const fs = await import("fs/promises");
|
|
651
|
+
const path = await import("path");
|
|
652
|
+
const dir = path.dirname(filePath);
|
|
653
|
+
await fs.mkdir(dir, { recursive: true });
|
|
654
|
+
await fs.writeFile(filePath, JSON.stringify(items), "utf8");
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ../src/sdk/modules/memory.ts
|
|
660
|
+
function isEndpointNotFound(error) {
|
|
661
|
+
return error instanceof RuntimeClientError && error.status === 404;
|
|
662
|
+
}
|
|
663
|
+
function toSotaType(memoryType) {
|
|
664
|
+
if (!memoryType) return void 0;
|
|
665
|
+
switch (memoryType) {
|
|
666
|
+
case "episodic":
|
|
667
|
+
return "event";
|
|
668
|
+
case "semantic":
|
|
669
|
+
return "factual";
|
|
670
|
+
case "procedural":
|
|
671
|
+
return "instruction";
|
|
672
|
+
default:
|
|
673
|
+
return memoryType;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function toLegacyType(memoryType) {
|
|
677
|
+
if (!memoryType) return void 0;
|
|
678
|
+
switch (memoryType) {
|
|
679
|
+
case "event":
|
|
680
|
+
return "episodic";
|
|
681
|
+
case "instruction":
|
|
682
|
+
return "procedural";
|
|
683
|
+
case "preference":
|
|
684
|
+
case "relationship":
|
|
685
|
+
case "opinion":
|
|
686
|
+
case "goal":
|
|
687
|
+
return "semantic";
|
|
688
|
+
default:
|
|
689
|
+
return memoryType;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
var MemoryModule = class {
|
|
693
|
+
constructor(client, cache, queue, options = {}) {
|
|
694
|
+
this.client = client;
|
|
695
|
+
this.cache = cache;
|
|
696
|
+
this.queue = queue;
|
|
697
|
+
this.options = options;
|
|
698
|
+
}
|
|
699
|
+
resolveProject(project) {
|
|
700
|
+
const value = project || this.options.defaultProject;
|
|
701
|
+
if (!value) {
|
|
702
|
+
throw new RuntimeClientError({
|
|
703
|
+
code: "MISSING_PROJECT",
|
|
704
|
+
message: "Project is required",
|
|
705
|
+
retryable: false
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
return value;
|
|
709
|
+
}
|
|
710
|
+
invalidate(project, userId, sessionId) {
|
|
711
|
+
if (this.options.cacheEnabled === false) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const scope = this.cache.makeScopeKey(project, userId, sessionId);
|
|
715
|
+
this.cache.invalidateScope(scope);
|
|
716
|
+
}
|
|
717
|
+
async add(params) {
|
|
718
|
+
const project = this.resolveProject(params.project);
|
|
719
|
+
const queueEnabled = this.options.queueEnabled !== false;
|
|
720
|
+
const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
|
|
721
|
+
if (useQueue) {
|
|
722
|
+
const queued = await this.queue.enqueue({
|
|
723
|
+
project,
|
|
724
|
+
userId: params.user_id,
|
|
725
|
+
sessionId: params.session_id,
|
|
726
|
+
payload: {
|
|
727
|
+
content: params.content,
|
|
728
|
+
memory_type: toSotaType(params.memory_type),
|
|
729
|
+
user_id: params.user_id,
|
|
730
|
+
session_id: params.session_id,
|
|
731
|
+
agent_id: params.agent_id,
|
|
732
|
+
importance: params.importance,
|
|
733
|
+
confidence: params.confidence,
|
|
734
|
+
metadata: params.metadata,
|
|
735
|
+
document_date: params.document_date,
|
|
736
|
+
event_date: params.event_date
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
740
|
+
return {
|
|
741
|
+
success: true,
|
|
742
|
+
mode: "async",
|
|
743
|
+
queued: true,
|
|
744
|
+
event_id: queued.eventId,
|
|
745
|
+
accepted_at: queued.createdAt
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
const response = await this.client.request({
|
|
750
|
+
endpoint: "/v1/memory",
|
|
751
|
+
method: "POST",
|
|
752
|
+
operation: "writeAck",
|
|
753
|
+
body: {
|
|
754
|
+
project,
|
|
755
|
+
content: params.content,
|
|
756
|
+
memory_type: toSotaType(params.memory_type),
|
|
757
|
+
user_id: params.user_id,
|
|
758
|
+
session_id: params.session_id,
|
|
759
|
+
agent_id: params.agent_id,
|
|
760
|
+
importance: params.importance,
|
|
761
|
+
confidence: params.confidence,
|
|
762
|
+
metadata: params.metadata,
|
|
763
|
+
document_date: params.document_date,
|
|
764
|
+
event_date: params.event_date,
|
|
765
|
+
write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
769
|
+
return {
|
|
770
|
+
success: true,
|
|
771
|
+
mode: "sync",
|
|
772
|
+
trace_id: response.trace_id || response.traceId,
|
|
773
|
+
memory_id: response.memory_id || response.memory?.id,
|
|
774
|
+
semantic_status: response.semantic_status || response.memory?.semantic_status,
|
|
775
|
+
pending_visibility: Boolean(response.pending_visibility),
|
|
776
|
+
visibility_sla_ms: response.visibility_sla_ms
|
|
777
|
+
};
|
|
778
|
+
} catch (error) {
|
|
779
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
await this.client.request({
|
|
783
|
+
endpoint: "/v1/memories",
|
|
784
|
+
method: "POST",
|
|
785
|
+
operation: "writeAck",
|
|
786
|
+
body: {
|
|
787
|
+
project,
|
|
788
|
+
content: params.content,
|
|
789
|
+
memory_type: toLegacyType(params.memory_type),
|
|
790
|
+
user_id: params.user_id,
|
|
791
|
+
session_id: params.session_id,
|
|
792
|
+
agent_id: params.agent_id,
|
|
793
|
+
importance: params.importance,
|
|
794
|
+
metadata: params.metadata
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
798
|
+
return {
|
|
799
|
+
success: true,
|
|
800
|
+
mode: "sync"
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async addBulk(params) {
|
|
805
|
+
const project = this.resolveProject(params.project);
|
|
806
|
+
if (!Array.isArray(params.memories) || params.memories.length === 0) {
|
|
807
|
+
throw new RuntimeClientError({
|
|
808
|
+
code: "VALIDATION_ERROR",
|
|
809
|
+
message: "memories is required",
|
|
810
|
+
retryable: false
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const queueEnabled = this.options.queueEnabled !== false;
|
|
814
|
+
const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
|
|
815
|
+
if (useQueue) {
|
|
816
|
+
const queued = await Promise.all(
|
|
817
|
+
params.memories.map(
|
|
818
|
+
(memory) => this.queue.enqueue({
|
|
819
|
+
project,
|
|
820
|
+
userId: memory.user_id,
|
|
821
|
+
sessionId: memory.session_id,
|
|
822
|
+
payload: {
|
|
823
|
+
content: memory.content,
|
|
824
|
+
memory_type: toSotaType(memory.memory_type),
|
|
825
|
+
user_id: memory.user_id,
|
|
826
|
+
session_id: memory.session_id,
|
|
827
|
+
agent_id: memory.agent_id,
|
|
828
|
+
importance: memory.importance,
|
|
829
|
+
confidence: memory.confidence,
|
|
830
|
+
metadata: memory.metadata,
|
|
831
|
+
document_date: memory.document_date,
|
|
832
|
+
event_date: memory.event_date
|
|
833
|
+
}
|
|
834
|
+
})
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
for (const memory of params.memories) {
|
|
838
|
+
this.invalidate(project, memory.user_id, memory.session_id);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
success: true,
|
|
842
|
+
mode: "async",
|
|
843
|
+
queued: true,
|
|
844
|
+
created: queued.length
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
const response = await this.client.request({
|
|
849
|
+
endpoint: "/v1/memory/bulk",
|
|
850
|
+
method: "POST",
|
|
851
|
+
operation: "bulk",
|
|
852
|
+
body: {
|
|
853
|
+
project,
|
|
854
|
+
memories: params.memories.map((memory) => ({
|
|
855
|
+
...memory,
|
|
856
|
+
memory_type: toSotaType(memory.memory_type)
|
|
857
|
+
})),
|
|
858
|
+
write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
for (const memory of params.memories) {
|
|
862
|
+
this.invalidate(project, memory.user_id, memory.session_id);
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
success: true,
|
|
866
|
+
mode: "sync",
|
|
867
|
+
trace_id: response.traceId
|
|
868
|
+
};
|
|
869
|
+
} catch (error) {
|
|
870
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
873
|
+
await Promise.all(
|
|
874
|
+
params.memories.map(
|
|
875
|
+
(memory) => this.add({
|
|
876
|
+
project,
|
|
877
|
+
...memory,
|
|
878
|
+
write_mode: "sync"
|
|
879
|
+
})
|
|
880
|
+
)
|
|
881
|
+
);
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
mode: "sync"
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async search(params) {
|
|
889
|
+
const project = this.resolveProject(params.project);
|
|
890
|
+
const topK = params.top_k || 10;
|
|
891
|
+
const profile = params.profile || "fast";
|
|
892
|
+
const includePending = params.include_pending !== false;
|
|
893
|
+
const cacheKey = this.cache.makeKey({
|
|
894
|
+
project,
|
|
895
|
+
userId: params.user_id,
|
|
896
|
+
sessionId: params.session_id,
|
|
897
|
+
query: params.query,
|
|
898
|
+
topK,
|
|
899
|
+
profile,
|
|
900
|
+
includePending
|
|
901
|
+
});
|
|
902
|
+
if (this.options.cacheEnabled !== false) {
|
|
903
|
+
const cached = this.cache.get(cacheKey);
|
|
904
|
+
if (cached) {
|
|
905
|
+
return {
|
|
906
|
+
...cached,
|
|
907
|
+
cache_hit: true
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const response = await this.client.request({
|
|
913
|
+
endpoint: "/v1/memory/search",
|
|
914
|
+
method: "POST",
|
|
915
|
+
operation: "search",
|
|
916
|
+
idempotent: true,
|
|
917
|
+
body: {
|
|
918
|
+
project,
|
|
919
|
+
query: params.query,
|
|
920
|
+
user_id: params.user_id,
|
|
921
|
+
session_id: params.session_id,
|
|
922
|
+
top_k: topK,
|
|
923
|
+
profile,
|
|
924
|
+
include_pending: includePending,
|
|
925
|
+
memory_types: params.memory_type ? [toSotaType(params.memory_type)] : void 0
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
const data = {
|
|
929
|
+
...response.data || {},
|
|
930
|
+
cache_hit: false
|
|
931
|
+
};
|
|
932
|
+
if (this.options.cacheEnabled !== false) {
|
|
933
|
+
const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
|
|
934
|
+
this.cache.set(cacheKey, scope, data);
|
|
935
|
+
}
|
|
936
|
+
return data;
|
|
937
|
+
} catch (error) {
|
|
938
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
const legacy = await this.client.request({
|
|
942
|
+
endpoint: "/v1/memories/search",
|
|
943
|
+
method: "POST",
|
|
944
|
+
operation: "search",
|
|
945
|
+
idempotent: true,
|
|
946
|
+
body: {
|
|
947
|
+
project,
|
|
948
|
+
query: params.query,
|
|
949
|
+
user_id: params.user_id,
|
|
950
|
+
session_id: params.session_id,
|
|
951
|
+
top_k: topK,
|
|
952
|
+
memory_type: toLegacyType(params.memory_type)
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
const data = {
|
|
956
|
+
...legacy.data || {},
|
|
957
|
+
cache_hit: false
|
|
958
|
+
};
|
|
959
|
+
if (this.options.cacheEnabled !== false) {
|
|
960
|
+
const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
|
|
961
|
+
this.cache.set(cacheKey, scope, data);
|
|
962
|
+
}
|
|
963
|
+
return data;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async getUserProfile(params) {
|
|
967
|
+
const project = this.resolveProject(params.project);
|
|
968
|
+
const query = new URLSearchParams({
|
|
969
|
+
project,
|
|
970
|
+
...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {},
|
|
971
|
+
...params.memory_types ? { memory_types: params.memory_types } : {}
|
|
972
|
+
});
|
|
973
|
+
try {
|
|
974
|
+
const response = await this.client.request({
|
|
975
|
+
endpoint: `/v1/memory/profile/${params.user_id}?${query}`,
|
|
976
|
+
method: "GET",
|
|
977
|
+
operation: "profile",
|
|
978
|
+
idempotent: true
|
|
979
|
+
});
|
|
980
|
+
return response.data;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
983
|
+
throw error;
|
|
984
|
+
}
|
|
985
|
+
const legacyQuery = new URLSearchParams({
|
|
986
|
+
project,
|
|
987
|
+
user_id: params.user_id,
|
|
988
|
+
limit: "200"
|
|
989
|
+
});
|
|
990
|
+
const legacy = await this.client.request({
|
|
991
|
+
endpoint: `/v1/memories?${legacyQuery}`,
|
|
992
|
+
method: "GET",
|
|
993
|
+
operation: "profile",
|
|
994
|
+
idempotent: true
|
|
995
|
+
});
|
|
996
|
+
const memories = Array.isArray(legacy.data?.memories) ? legacy.data.memories : [];
|
|
997
|
+
return {
|
|
998
|
+
user_id: params.user_id,
|
|
999
|
+
memories,
|
|
1000
|
+
count: memories.length
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async getSessionMemories(params) {
|
|
1005
|
+
const project = this.resolveProject(params.project);
|
|
1006
|
+
const query = new URLSearchParams({
|
|
1007
|
+
project,
|
|
1008
|
+
...params.limit ? { limit: String(params.limit) } : {},
|
|
1009
|
+
...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {}
|
|
1010
|
+
});
|
|
1011
|
+
const response = await this.client.request({
|
|
1012
|
+
endpoint: `/v1/memory/session/${params.session_id}?${query}`,
|
|
1013
|
+
method: "GET",
|
|
1014
|
+
operation: "profile",
|
|
1015
|
+
idempotent: true
|
|
1016
|
+
});
|
|
1017
|
+
return response.data;
|
|
1018
|
+
}
|
|
1019
|
+
async get(memoryId) {
|
|
1020
|
+
try {
|
|
1021
|
+
const response = await this.client.request({
|
|
1022
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1023
|
+
method: "GET",
|
|
1024
|
+
operation: "get",
|
|
1025
|
+
idempotent: true
|
|
1026
|
+
});
|
|
1027
|
+
return response.data;
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
1032
|
+
const legacy = await this.client.request({
|
|
1033
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1034
|
+
method: "GET",
|
|
1035
|
+
operation: "get",
|
|
1036
|
+
idempotent: true
|
|
1037
|
+
});
|
|
1038
|
+
return legacy.data;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
async update(memoryId, params) {
|
|
1042
|
+
try {
|
|
1043
|
+
await this.client.request({
|
|
1044
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1045
|
+
method: "PUT",
|
|
1046
|
+
operation: "writeAck",
|
|
1047
|
+
body: params
|
|
1048
|
+
});
|
|
1049
|
+
return { success: true };
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
await this.client.request({
|
|
1055
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1056
|
+
method: "PUT",
|
|
1057
|
+
operation: "writeAck",
|
|
1058
|
+
body: { content: params.content }
|
|
1059
|
+
});
|
|
1060
|
+
return { success: true };
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async delete(memoryId) {
|
|
1064
|
+
try {
|
|
1065
|
+
await this.client.request({
|
|
1066
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1067
|
+
method: "DELETE",
|
|
1068
|
+
operation: "writeAck"
|
|
1069
|
+
});
|
|
1070
|
+
return { success: true, deleted: memoryId };
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1073
|
+
throw error;
|
|
1074
|
+
}
|
|
1075
|
+
await this.client.request({
|
|
1076
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1077
|
+
method: "DELETE",
|
|
1078
|
+
operation: "writeAck"
|
|
1079
|
+
});
|
|
1080
|
+
return { success: true, deleted: memoryId };
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async flag(params) {
|
|
1084
|
+
try {
|
|
1085
|
+
await this.client.request({
|
|
1086
|
+
endpoint: `/v1/memory/${params.memoryId}/flag`,
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
operation: "writeAck",
|
|
1089
|
+
body: {
|
|
1090
|
+
reason: params.reason,
|
|
1091
|
+
severity: params.severity || "medium"
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
return { success: true };
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
await this.client.request({
|
|
1100
|
+
endpoint: `/v1/memory/${params.memoryId}`,
|
|
1101
|
+
method: "PUT",
|
|
1102
|
+
operation: "writeAck",
|
|
1103
|
+
body: {
|
|
1104
|
+
content: `[FLAGGED:${params.severity || "medium"}] ${params.reason}`
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
return { success: true };
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
// ../src/sdk/modules/session.ts
|
|
1113
|
+
function randomSessionId() {
|
|
1114
|
+
return `sess_${stableHash(`${Date.now()}_${Math.random()}`)}`;
|
|
1115
|
+
}
|
|
1116
|
+
function assertTransition(current, next) {
|
|
1117
|
+
const allowed = {
|
|
1118
|
+
created: ["active", "ended", "archived"],
|
|
1119
|
+
active: ["suspended", "ended", "archived"],
|
|
1120
|
+
suspended: ["resumed", "ended", "archived"],
|
|
1121
|
+
resumed: ["suspended", "ended", "archived"],
|
|
1122
|
+
ended: ["archived"],
|
|
1123
|
+
archived: []
|
|
1124
|
+
};
|
|
1125
|
+
if (!allowed[current].includes(next)) {
|
|
1126
|
+
throw new RuntimeClientError({
|
|
1127
|
+
code: "INVALID_SESSION_STATE",
|
|
1128
|
+
message: `Invalid session transition ${current} -> ${next}`,
|
|
1129
|
+
retryable: false
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
var SessionModule = class {
|
|
1134
|
+
constructor(memory, defaultProject) {
|
|
1135
|
+
this.memory = memory;
|
|
1136
|
+
this.defaultProject = defaultProject;
|
|
1137
|
+
}
|
|
1138
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1139
|
+
resolveProject(project) {
|
|
1140
|
+
const value = project || this.defaultProject;
|
|
1141
|
+
if (!value) {
|
|
1142
|
+
throw new RuntimeClientError({
|
|
1143
|
+
code: "MISSING_PROJECT",
|
|
1144
|
+
message: "Project is required",
|
|
1145
|
+
retryable: false
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
ensure(sessionId) {
|
|
1151
|
+
const found = this.sessions.get(sessionId);
|
|
1152
|
+
if (!found) {
|
|
1153
|
+
throw new RuntimeClientError({
|
|
1154
|
+
code: "SESSION_NOT_FOUND",
|
|
1155
|
+
message: `Unknown session ${sessionId}`,
|
|
1156
|
+
retryable: false
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
return found;
|
|
1160
|
+
}
|
|
1161
|
+
async start(params) {
|
|
1162
|
+
const project = this.resolveProject(params.project);
|
|
1163
|
+
const sessionId = params.sessionId || randomSessionId();
|
|
1164
|
+
const now = nowIso();
|
|
1165
|
+
const record = {
|
|
1166
|
+
sessionId,
|
|
1167
|
+
project,
|
|
1168
|
+
userId: params.userId,
|
|
1169
|
+
state: "active",
|
|
1170
|
+
sequence: 0,
|
|
1171
|
+
metadata: params.metadata,
|
|
1172
|
+
createdAt: now,
|
|
1173
|
+
updatedAt: now
|
|
1174
|
+
};
|
|
1175
|
+
this.sessions.set(sessionId, record);
|
|
1176
|
+
return {
|
|
1177
|
+
sessionId,
|
|
1178
|
+
state: record.state,
|
|
1179
|
+
createdAt: now
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
async event(params) {
|
|
1183
|
+
const session = this.ensure(params.sessionId);
|
|
1184
|
+
if (session.state !== "active" && session.state !== "resumed") {
|
|
1185
|
+
throw new RuntimeClientError({
|
|
1186
|
+
code: "INVALID_SESSION_STATE",
|
|
1187
|
+
message: `Cannot append event in ${session.state} state`,
|
|
1188
|
+
retryable: false
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
session.sequence += 1;
|
|
1192
|
+
session.updatedAt = nowIso();
|
|
1193
|
+
const eventId = `evt_${stableHash(JSON.stringify({
|
|
1194
|
+
sessionId: session.sessionId,
|
|
1195
|
+
seq: session.sequence,
|
|
1196
|
+
type: params.type,
|
|
1197
|
+
content: params.content,
|
|
1198
|
+
parent: params.parentEventId || ""
|
|
1199
|
+
}))}`;
|
|
1200
|
+
await this.memory.add({
|
|
1201
|
+
project: session.project,
|
|
1202
|
+
content: `${params.type}: ${params.content}`,
|
|
1203
|
+
memory_type: "event",
|
|
1204
|
+
user_id: session.userId,
|
|
1205
|
+
session_id: session.sessionId,
|
|
1206
|
+
metadata: {
|
|
1207
|
+
session_event: true,
|
|
1208
|
+
event_id: eventId,
|
|
1209
|
+
sequence: session.sequence,
|
|
1210
|
+
parent_event_id: params.parentEventId,
|
|
1211
|
+
...session.metadata,
|
|
1212
|
+
...params.metadata || {}
|
|
1213
|
+
},
|
|
1214
|
+
write_mode: "async"
|
|
1215
|
+
});
|
|
1216
|
+
return {
|
|
1217
|
+
success: true,
|
|
1218
|
+
eventId,
|
|
1219
|
+
sequence: session.sequence
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
async suspend(params) {
|
|
1223
|
+
const session = this.ensure(params.sessionId);
|
|
1224
|
+
assertTransition(session.state, "suspended");
|
|
1225
|
+
session.state = "suspended";
|
|
1226
|
+
session.updatedAt = nowIso();
|
|
1227
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1228
|
+
}
|
|
1229
|
+
async resume(params) {
|
|
1230
|
+
const session = this.ensure(params.sessionId);
|
|
1231
|
+
const target = session.state === "suspended" ? "resumed" : "active";
|
|
1232
|
+
assertTransition(session.state, target);
|
|
1233
|
+
session.state = target;
|
|
1234
|
+
session.updatedAt = nowIso();
|
|
1235
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1236
|
+
}
|
|
1237
|
+
async end(params) {
|
|
1238
|
+
const session = this.ensure(params.sessionId);
|
|
1239
|
+
assertTransition(session.state, "ended");
|
|
1240
|
+
session.state = "ended";
|
|
1241
|
+
session.updatedAt = nowIso();
|
|
1242
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1243
|
+
}
|
|
1244
|
+
async archive(params) {
|
|
1245
|
+
const session = this.ensure(params.sessionId);
|
|
1246
|
+
assertTransition(session.state, "archived");
|
|
1247
|
+
session.state = "archived";
|
|
1248
|
+
session.updatedAt = nowIso();
|
|
1249
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// ../src/sdk/modules/profile.ts
|
|
1254
|
+
var ProfileModule = class {
|
|
1255
|
+
constructor(memory) {
|
|
1256
|
+
this.memory = memory;
|
|
1257
|
+
}
|
|
1258
|
+
async getUserProfile(params) {
|
|
1259
|
+
return this.memory.getUserProfile(params);
|
|
1260
|
+
}
|
|
1261
|
+
async getSessionMemories(params) {
|
|
1262
|
+
return this.memory.getSessionMemories(params);
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
// ../src/sdk/modules/analytics.ts
|
|
1267
|
+
var AnalyticsModule = class {
|
|
1268
|
+
constructor(diagnostics, queue) {
|
|
1269
|
+
this.diagnostics = diagnostics;
|
|
1270
|
+
this.queue = queue;
|
|
1271
|
+
}
|
|
1272
|
+
diagnosticsSnapshot() {
|
|
1273
|
+
return this.diagnostics.snapshot();
|
|
1274
|
+
}
|
|
1275
|
+
queueStatus() {
|
|
1276
|
+
return this.queue.status();
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// ../src/sdk/agent-runtime.ts
|
|
1281
|
+
function detectBrowserStorage() {
|
|
1282
|
+
const maybeStorage = globalThis.localStorage;
|
|
1283
|
+
if (!maybeStorage || typeof maybeStorage !== "object") return null;
|
|
1284
|
+
const candidate = maybeStorage;
|
|
1285
|
+
if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
getItem: candidate.getItem,
|
|
1290
|
+
setItem: candidate.setItem
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function createBindingStore(filePath) {
|
|
1294
|
+
const storage = detectBrowserStorage();
|
|
1295
|
+
if (storage) {
|
|
1296
|
+
const key = "whisper_agent_runtime_bindings";
|
|
1297
|
+
return {
|
|
1298
|
+
async load() {
|
|
1299
|
+
const raw = storage.getItem(key);
|
|
1300
|
+
if (!raw) return {};
|
|
1301
|
+
try {
|
|
1302
|
+
const parsed = JSON.parse(raw);
|
|
1303
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1304
|
+
} catch {
|
|
1305
|
+
return {};
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
async save(bindings) {
|
|
1309
|
+
storage.setItem(key, JSON.stringify(bindings));
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
async load() {
|
|
1315
|
+
if (typeof process === "undefined") return {};
|
|
1316
|
+
const fs = await import("fs/promises");
|
|
1317
|
+
const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
|
|
1318
|
+
try {
|
|
1319
|
+
const raw = await fs.readFile(path, "utf8");
|
|
1320
|
+
const parsed = JSON.parse(raw);
|
|
1321
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1322
|
+
} catch {
|
|
1323
|
+
return {};
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
async save(bindings) {
|
|
1327
|
+
if (typeof process === "undefined") return;
|
|
1328
|
+
const fs = await import("fs/promises");
|
|
1329
|
+
const pathMod = await import("path");
|
|
1330
|
+
const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
|
|
1331
|
+
await fs.mkdir(pathMod.dirname(path), { recursive: true });
|
|
1332
|
+
await fs.writeFile(path, JSON.stringify(bindings), "utf8");
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function normalizeWorkspacePath(value) {
|
|
1337
|
+
const trimmed = value?.trim();
|
|
1338
|
+
if (!trimmed) return null;
|
|
1339
|
+
return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1340
|
+
}
|
|
1341
|
+
function pathBase(value) {
|
|
1342
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1343
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
1344
|
+
return parts[parts.length - 1] || normalized;
|
|
1345
|
+
}
|
|
1346
|
+
function defaultSalience(kind, success) {
|
|
1347
|
+
if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
|
|
1348
|
+
if (kind === "outcome" || kind === "task_update") return "medium";
|
|
1349
|
+
if (kind === "tool_result" && success === false) return "high";
|
|
1350
|
+
return "low";
|
|
1351
|
+
}
|
|
1352
|
+
function toMemoryType(kind) {
|
|
1353
|
+
if (kind === "decision" || kind === "constraint") return "instruction";
|
|
1354
|
+
return "event";
|
|
1355
|
+
}
|
|
1356
|
+
function summarizeLowSalience(events) {
|
|
1357
|
+
const lines = events.slice(-10).map((event) => {
|
|
1358
|
+
const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
|
|
1359
|
+
const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
|
|
1360
|
+
return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
|
|
1361
|
+
});
|
|
1362
|
+
return `Recent low-salience work:
|
|
1363
|
+
${lines.join("\n")}`;
|
|
1364
|
+
}
|
|
1365
|
+
function compactWhitespace(value) {
|
|
1366
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1367
|
+
}
|
|
1368
|
+
function normalizeSummary(value) {
|
|
1369
|
+
return compactWhitespace(String(value || "").toLowerCase());
|
|
1370
|
+
}
|
|
1371
|
+
function tokenize(value) {
|
|
1372
|
+
return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
|
|
1373
|
+
}
|
|
1374
|
+
function jaccardOverlap(left, right) {
|
|
1375
|
+
const leftTokens = new Set(tokenize(left));
|
|
1376
|
+
const rightTokens = new Set(tokenize(right));
|
|
1377
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
|
|
1378
|
+
let intersection = 0;
|
|
1379
|
+
for (const token of leftTokens) {
|
|
1380
|
+
if (rightTokens.has(token)) intersection += 1;
|
|
1381
|
+
}
|
|
1382
|
+
const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
|
|
1383
|
+
return union > 0 ? intersection / union : 0;
|
|
1384
|
+
}
|
|
1385
|
+
function clamp01(value) {
|
|
1386
|
+
if (!Number.isFinite(value)) return 0;
|
|
1387
|
+
if (value < 0) return 0;
|
|
1388
|
+
if (value > 1) return 1;
|
|
1389
|
+
return value;
|
|
1390
|
+
}
|
|
1391
|
+
function withTimeout(promise, timeoutMs) {
|
|
1392
|
+
return new Promise((resolve, reject) => {
|
|
1393
|
+
const timeout = setTimeout(() => {
|
|
1394
|
+
reject(new Error("timeout"));
|
|
1395
|
+
}, timeoutMs);
|
|
1396
|
+
promise.then(
|
|
1397
|
+
(value) => {
|
|
1398
|
+
clearTimeout(timeout);
|
|
1399
|
+
resolve(value);
|
|
1400
|
+
},
|
|
1401
|
+
(error) => {
|
|
1402
|
+
clearTimeout(timeout);
|
|
1403
|
+
reject(error);
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
function extractTimestamp(metadata) {
|
|
1409
|
+
const candidates = [
|
|
1410
|
+
metadata?.updatedAt,
|
|
1411
|
+
metadata?.createdAt,
|
|
1412
|
+
metadata?.timestamp,
|
|
1413
|
+
metadata?.event_date,
|
|
1414
|
+
metadata?.eventDate
|
|
1415
|
+
];
|
|
1416
|
+
for (const value of candidates) {
|
|
1417
|
+
if (typeof value === "string") {
|
|
1418
|
+
const parsed = Date.parse(value);
|
|
1419
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return 0;
|
|
1423
|
+
}
|
|
1424
|
+
var DEFAULT_RANK_WEIGHTS = {
|
|
1425
|
+
focusedPassBonus: 0.2,
|
|
1426
|
+
sourceMatchBonus: 0.18,
|
|
1427
|
+
touchedFileBonus: 0.12,
|
|
1428
|
+
clientMatchBonus: 0.1,
|
|
1429
|
+
highSalienceBonus: 0.12,
|
|
1430
|
+
mediumSalienceBonus: 0.06,
|
|
1431
|
+
staleBroadPenalty: -0.1,
|
|
1432
|
+
unrelatedClientPenalty: -0.18,
|
|
1433
|
+
lowSaliencePenalty: -0.12
|
|
1434
|
+
};
|
|
1435
|
+
var DEFAULT_SOURCE_ACTIVITY = {
|
|
1436
|
+
maxTurns: 10,
|
|
1437
|
+
maxIdleMs: 30 * 60 * 1e3,
|
|
1438
|
+
decayAfterTurns: 5,
|
|
1439
|
+
decayAfterIdleMs: 15 * 60 * 1e3,
|
|
1440
|
+
evictOnTaskSwitch: true
|
|
1441
|
+
};
|
|
1442
|
+
var WhisperAgentRuntime = class {
|
|
1443
|
+
constructor(args) {
|
|
1444
|
+
this.args = args;
|
|
1445
|
+
this.bindingStore = createBindingStore(args.options.bindingStorePath);
|
|
1446
|
+
const retrieval = args.options.retrieval || {};
|
|
1447
|
+
this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
|
|
1448
|
+
this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
|
|
1449
|
+
this.maxTokens = args.options.maxTokens ?? 4e3;
|
|
1450
|
+
this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
|
|
1451
|
+
this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
|
|
1452
|
+
this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
|
|
1453
|
+
this.baseContext = args.baseContext;
|
|
1454
|
+
this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
|
|
1455
|
+
this.minFocusedResults = retrieval.minFocusedResults ?? 3;
|
|
1456
|
+
this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
|
|
1457
|
+
this.minProjectScore = retrieval.minProjectScore ?? 0.5;
|
|
1458
|
+
this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
|
|
1459
|
+
this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
|
|
1460
|
+
this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
|
|
1461
|
+
}
|
|
1462
|
+
bindingStore;
|
|
1463
|
+
focusedTopK;
|
|
1464
|
+
broadTopK;
|
|
1465
|
+
maxTokens;
|
|
1466
|
+
targetRetrievalMs;
|
|
1467
|
+
hardRetrievalTimeoutMs;
|
|
1468
|
+
recentWorkLimit;
|
|
1469
|
+
baseContext;
|
|
1470
|
+
clientName;
|
|
1471
|
+
minFocusedResults;
|
|
1472
|
+
minFocusedTopScore;
|
|
1473
|
+
minProjectScore;
|
|
1474
|
+
minMemoryScore;
|
|
1475
|
+
rankWeights;
|
|
1476
|
+
sourceActivityOptions;
|
|
1477
|
+
bindings = null;
|
|
1478
|
+
touchedFiles = [];
|
|
1479
|
+
recentWork = [];
|
|
1480
|
+
recentSourceActivity = [];
|
|
1481
|
+
bufferedLowSalience = [];
|
|
1482
|
+
lastPreparedTurn = null;
|
|
1483
|
+
mergedCount = 0;
|
|
1484
|
+
droppedCount = 0;
|
|
1485
|
+
focusedPassHits = 0;
|
|
1486
|
+
fallbackTriggers = 0;
|
|
1487
|
+
floorDroppedCount = 0;
|
|
1488
|
+
injectedItemCount = 0;
|
|
1489
|
+
sourceScopedTurns = 0;
|
|
1490
|
+
broadScopedTurns = 0;
|
|
1491
|
+
totalTurns = 0;
|
|
1492
|
+
currentTurn = 0;
|
|
1493
|
+
lastTaskSummary = "";
|
|
1494
|
+
lastScope = {};
|
|
1495
|
+
async getBindings() {
|
|
1496
|
+
if (!this.bindings) {
|
|
1497
|
+
this.bindings = await this.bindingStore.load();
|
|
1498
|
+
}
|
|
1499
|
+
return this.bindings;
|
|
1500
|
+
}
|
|
1501
|
+
pushTouchedFiles(paths) {
|
|
1502
|
+
if (!paths || paths.length === 0) return;
|
|
1503
|
+
for (const path of paths) {
|
|
1504
|
+
if (!path) continue;
|
|
1505
|
+
this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
pushWorkEvent(event) {
|
|
1509
|
+
this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
|
|
1510
|
+
}
|
|
1511
|
+
noteSourceActivity(sourceIds) {
|
|
1512
|
+
const now = Date.now();
|
|
1513
|
+
for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
|
|
1514
|
+
this.recentSourceActivity = [
|
|
1515
|
+
...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
|
|
1516
|
+
{ sourceId, turn: this.currentTurn, at: now }
|
|
1517
|
+
].slice(-24);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
refreshTaskSummary(taskSummary) {
|
|
1521
|
+
const next = normalizeSummary(taskSummary);
|
|
1522
|
+
if (!next) return;
|
|
1523
|
+
if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
|
|
1524
|
+
this.recentSourceActivity = [];
|
|
1525
|
+
}
|
|
1526
|
+
this.lastTaskSummary = next;
|
|
1527
|
+
}
|
|
1528
|
+
activeSourceIds() {
|
|
1529
|
+
const now = Date.now();
|
|
1530
|
+
const active = /* @__PURE__ */ new Map();
|
|
1531
|
+
const maxTurns = this.sourceActivityOptions.maxTurns;
|
|
1532
|
+
const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
|
|
1533
|
+
const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
|
|
1534
|
+
const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
|
|
1535
|
+
const fresh = [];
|
|
1536
|
+
for (const entry of this.recentSourceActivity) {
|
|
1537
|
+
const turnDelta = this.currentTurn - entry.turn;
|
|
1538
|
+
const idleDelta = now - entry.at;
|
|
1539
|
+
if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
|
|
1540
|
+
fresh.push(entry);
|
|
1541
|
+
let weight = 1;
|
|
1542
|
+
if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
|
|
1543
|
+
weight = 0.5;
|
|
1544
|
+
}
|
|
1545
|
+
const current = active.get(entry.sourceId) || 0;
|
|
1546
|
+
active.set(entry.sourceId, Math.max(current, weight));
|
|
1547
|
+
}
|
|
1548
|
+
this.recentSourceActivity = fresh.slice(-24);
|
|
1549
|
+
return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
|
|
1550
|
+
}
|
|
1551
|
+
focusedScope(input) {
|
|
1552
|
+
const sourceIds = this.activeSourceIds();
|
|
1553
|
+
const fileHints = [...new Set([
|
|
1554
|
+
...input.touchedFiles || [],
|
|
1555
|
+
...this.touchedFiles,
|
|
1556
|
+
...this.recentWork.flatMap((event) => event.filePaths || [])
|
|
1557
|
+
].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
|
|
1558
|
+
return {
|
|
1559
|
+
sourceIds,
|
|
1560
|
+
fileHints,
|
|
1561
|
+
clientName: this.clientName || void 0
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
exactFileMetadataFilter(fileHints) {
|
|
1565
|
+
const exact = fileHints.find((value) => /[\\/]/.test(value));
|
|
1566
|
+
if (!exact) return void 0;
|
|
1567
|
+
return { filePath: exact };
|
|
1568
|
+
}
|
|
1569
|
+
makeTaskFrameQuery(input) {
|
|
1570
|
+
const task = compactWhitespace(input.taskSummary || "");
|
|
1571
|
+
const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
|
|
1572
|
+
const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
|
|
1573
|
+
const parts = [
|
|
1574
|
+
task ? `task ${task}` : "",
|
|
1575
|
+
salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
|
|
1576
|
+
files.length > 0 ? `files ${files.join(" ")}` : "",
|
|
1577
|
+
input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
|
|
1578
|
+
].filter(Boolean);
|
|
1579
|
+
if (parts.length === 0) return null;
|
|
1580
|
+
return parts.join(" | ");
|
|
1581
|
+
}
|
|
1582
|
+
async resolveScope(overrides) {
|
|
1583
|
+
const merged = {
|
|
1584
|
+
...this.baseContext,
|
|
1585
|
+
...overrides
|
|
1586
|
+
};
|
|
1587
|
+
const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
|
|
1588
|
+
const bindings = await this.getBindings();
|
|
1589
|
+
const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
|
|
1590
|
+
const configuredProject = merged.project;
|
|
1591
|
+
let projectRef = configuredProject;
|
|
1592
|
+
let projectSource = overrides?.project ? "explicit" : "generated";
|
|
1593
|
+
let warning;
|
|
1594
|
+
if (workspaceProject) {
|
|
1595
|
+
projectRef = workspaceProject;
|
|
1596
|
+
projectSource = "workspace";
|
|
1597
|
+
if (configuredProject && workspaceProject !== configuredProject) {
|
|
1598
|
+
warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
|
|
1599
|
+
}
|
|
1600
|
+
} else if (configuredProject) {
|
|
1601
|
+
projectRef = configuredProject;
|
|
1602
|
+
projectSource = overrides?.project ? "explicit" : "config";
|
|
1603
|
+
}
|
|
1604
|
+
const project = (await this.args.adapter.resolveProject(projectRef)).id;
|
|
1605
|
+
if (normalizedWorkspace) {
|
|
1606
|
+
bindings[normalizedWorkspace] = project;
|
|
1607
|
+
await this.bindingStore.save(bindings);
|
|
1608
|
+
}
|
|
1609
|
+
const scope = {
|
|
1610
|
+
...merged,
|
|
1611
|
+
project,
|
|
1612
|
+
userId: merged.userId || `${this.clientName}-user`,
|
|
1613
|
+
sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
|
|
1614
|
+
};
|
|
1615
|
+
this.lastScope = {
|
|
1616
|
+
project: scope.project,
|
|
1617
|
+
userId: scope.userId,
|
|
1618
|
+
sessionId: scope.sessionId,
|
|
1619
|
+
source: projectSource,
|
|
1620
|
+
warning
|
|
1621
|
+
};
|
|
1622
|
+
return { scope, projectSource, warning };
|
|
1623
|
+
}
|
|
1624
|
+
async runBranch(name, task) {
|
|
1625
|
+
const startedAt = Date.now();
|
|
1626
|
+
try {
|
|
1627
|
+
const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
|
|
1628
|
+
return {
|
|
1629
|
+
name,
|
|
1630
|
+
status: "ok",
|
|
1631
|
+
durationMs: Date.now() - startedAt,
|
|
1632
|
+
value
|
|
1633
|
+
};
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
const durationMs = Date.now() - startedAt;
|
|
1636
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1637
|
+
return {
|
|
1638
|
+
name,
|
|
1639
|
+
status: reason === "timeout" ? "timeout" : "error",
|
|
1640
|
+
durationMs,
|
|
1641
|
+
reason
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
contextItems(result, sourceQuery, pass) {
|
|
1646
|
+
const sourceScope = result.meta?.source_scope;
|
|
1647
|
+
if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
|
|
1648
|
+
this.noteSourceActivity(sourceScope.source_ids || []);
|
|
1649
|
+
}
|
|
1650
|
+
return (result.results || []).map((item) => ({
|
|
1651
|
+
id: item.id,
|
|
1652
|
+
content: item.content,
|
|
1653
|
+
type: "project",
|
|
1654
|
+
score: item.score ?? 0,
|
|
1655
|
+
sourceQuery,
|
|
1656
|
+
pass,
|
|
1657
|
+
metadata: item.metadata || {}
|
|
1658
|
+
}));
|
|
1659
|
+
}
|
|
1660
|
+
memoryItems(result, sourceQuery, pass) {
|
|
1661
|
+
return (result.results || []).map((item, index) => ({
|
|
1662
|
+
id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
|
|
1663
|
+
content: item.chunk?.content || item.memory?.content || "",
|
|
1664
|
+
type: "memory",
|
|
1665
|
+
score: item.similarity ?? 0,
|
|
1666
|
+
sourceQuery,
|
|
1667
|
+
pass,
|
|
1668
|
+
metadata: {
|
|
1669
|
+
...item.chunk?.metadata || {},
|
|
1670
|
+
...item.memory?.temporal || {},
|
|
1671
|
+
confidence: item.memory?.confidence
|
|
1672
|
+
}
|
|
1673
|
+
})).filter((item) => item.content);
|
|
1674
|
+
}
|
|
1675
|
+
stableItemKey(item) {
|
|
1676
|
+
const metadata = item.metadata || {};
|
|
1677
|
+
const sourceId = String(metadata.source_id || "");
|
|
1678
|
+
const documentId = String(metadata.document_id || metadata.documentId || "");
|
|
1679
|
+
const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
|
|
1680
|
+
return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
|
|
1681
|
+
}
|
|
1682
|
+
metadataStrings(item) {
|
|
1683
|
+
const metadata = item.metadata || {};
|
|
1684
|
+
return [
|
|
1685
|
+
metadata.filePath,
|
|
1686
|
+
metadata.file_path,
|
|
1687
|
+
metadata.path,
|
|
1688
|
+
metadata.section_path,
|
|
1689
|
+
metadata.parent_section_path,
|
|
1690
|
+
metadata.web_url,
|
|
1691
|
+
metadata.url
|
|
1692
|
+
].map((value) => String(value || "").toLowerCase()).filter(Boolean);
|
|
1693
|
+
}
|
|
1694
|
+
hasSourceMatch(item, scope) {
|
|
1695
|
+
const sourceId = String(item.metadata?.source_id || "");
|
|
1696
|
+
return Boolean(sourceId && scope.sourceIds.includes(sourceId));
|
|
1697
|
+
}
|
|
1698
|
+
hasFileMatch(item, scope) {
|
|
1699
|
+
if (scope.fileHints.length === 0) return false;
|
|
1700
|
+
const metadata = this.metadataStrings(item);
|
|
1701
|
+
const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
|
|
1702
|
+
return lowerHints.some((hint) => {
|
|
1703
|
+
const base = pathBase(hint).toLowerCase();
|
|
1704
|
+
return metadata.some((value) => value.includes(hint) || value.endsWith(base));
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
hasClientMatch(item, scope) {
|
|
1708
|
+
const itemClient = String(item.metadata?.client_name || "");
|
|
1709
|
+
return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
|
|
1710
|
+
}
|
|
1711
|
+
salienceAdjustment(item) {
|
|
1712
|
+
const salience = item.metadata?.salience;
|
|
1713
|
+
if (salience === "high") return this.rankWeights.highSalienceBonus;
|
|
1714
|
+
if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
|
|
1715
|
+
if (salience === "low") return this.rankWeights.lowSaliencePenalty;
|
|
1716
|
+
return 0;
|
|
1717
|
+
}
|
|
1718
|
+
narrowFocusedMemories(items, scope) {
|
|
1719
|
+
const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
|
|
1720
|
+
if (!hasSignals) return items;
|
|
1721
|
+
const narrowed = items.filter((item) => {
|
|
1722
|
+
const matchesClient = this.hasClientMatch(item, scope);
|
|
1723
|
+
const matchesFile = this.hasFileMatch(item, scope);
|
|
1724
|
+
const matchesSource = this.hasSourceMatch(item, scope);
|
|
1725
|
+
const salience = item.metadata?.salience;
|
|
1726
|
+
if (scope.clientName && item.metadata?.client_name && !matchesClient) {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
if (salience === "low" && !matchesFile && !matchesSource) {
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
return matchesClient || matchesFile || matchesSource || !scope.clientName;
|
|
1733
|
+
});
|
|
1734
|
+
return narrowed.length > 0 ? narrowed : items;
|
|
1735
|
+
}
|
|
1736
|
+
applyRelevanceFloor(items) {
|
|
1737
|
+
const filtered = items.filter(
|
|
1738
|
+
(item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
|
|
1739
|
+
);
|
|
1740
|
+
return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
|
|
1741
|
+
}
|
|
1742
|
+
rerank(items, scope) {
|
|
1743
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
1744
|
+
for (const item of items) {
|
|
1745
|
+
const key = this.stableItemKey(item);
|
|
1746
|
+
const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
|
|
1747
|
+
const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
|
|
1748
|
+
const sourceMatch = this.hasSourceMatch(item, scope);
|
|
1749
|
+
const fileMatch = this.hasFileMatch(item, scope);
|
|
1750
|
+
const clientMatch = this.hasClientMatch(item, scope);
|
|
1751
|
+
const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
|
|
1752
|
+
const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
|
|
1753
|
+
const next = {
|
|
1754
|
+
...item,
|
|
1755
|
+
score: clamp01(
|
|
1756
|
+
item.score + queryBonus + recency + (item.pass === "focused" ? this.rankWeights.focusedPassBonus : 0) + (sourceMatch ? this.rankWeights.sourceMatchBonus : 0) + (fileMatch ? this.rankWeights.touchedFileBonus : 0) + (clientMatch ? this.rankWeights.clientMatchBonus : 0) + this.salienceAdjustment(item) + broadPenalty + clientPenalty
|
|
1757
|
+
)
|
|
1758
|
+
};
|
|
1759
|
+
const existing = deduped.get(key);
|
|
1760
|
+
if (!existing || next.score > existing.score) {
|
|
1761
|
+
deduped.set(key, next);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return {
|
|
1765
|
+
items: [...deduped.values()].sort((left, right) => right.score - left.score),
|
|
1766
|
+
dedupedCount: Math.max(0, items.length - deduped.size)
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
buildContext(items) {
|
|
1770
|
+
const maxChars = this.maxTokens * 4;
|
|
1771
|
+
const lines = [];
|
|
1772
|
+
let used = 0;
|
|
1773
|
+
for (const item of items) {
|
|
1774
|
+
const label = item.type === "memory" ? "memory" : "context";
|
|
1775
|
+
const content = compactWhitespace(item.content);
|
|
1776
|
+
if (!content) continue;
|
|
1777
|
+
const line = `[${label}] ${content}`;
|
|
1778
|
+
if (used + line.length > maxChars) break;
|
|
1779
|
+
lines.push(line);
|
|
1780
|
+
used += line.length;
|
|
1781
|
+
}
|
|
1782
|
+
if (lines.length === 0) return "";
|
|
1783
|
+
return `Relevant context:
|
|
1784
|
+
${lines.join("\n")}`;
|
|
1785
|
+
}
|
|
1786
|
+
async bootstrap(context = {}) {
|
|
1787
|
+
const { scope, warning } = await this.resolveScope(context);
|
|
1788
|
+
const warnings = warning ? [warning] : [];
|
|
1789
|
+
const startedAt = Date.now();
|
|
1790
|
+
const branches = await Promise.all([
|
|
1791
|
+
this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
|
|
1792
|
+
project: scope.project,
|
|
1793
|
+
session_id: scope.sessionId,
|
|
1794
|
+
include_pending: true,
|
|
1795
|
+
limit: 12
|
|
1796
|
+
})),
|
|
1797
|
+
this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
|
|
1798
|
+
project: scope.project,
|
|
1799
|
+
user_id: scope.userId,
|
|
1800
|
+
include_pending: true,
|
|
1801
|
+
memory_types: "preference,instruction,goal"
|
|
1802
|
+
}) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
|
|
1803
|
+
this.runBranch("project_rules", () => this.args.adapter.query({
|
|
1804
|
+
project: scope.project,
|
|
1805
|
+
query: "project rules instructions constraints conventions open threads",
|
|
1806
|
+
top_k: this.focusedTopK,
|
|
1807
|
+
include_memories: false,
|
|
1808
|
+
user_id: scope.userId,
|
|
1809
|
+
session_id: scope.sessionId,
|
|
1810
|
+
max_tokens: this.maxTokens,
|
|
1811
|
+
compress: true,
|
|
1812
|
+
compression_strategy: "adaptive"
|
|
1813
|
+
}))
|
|
1814
|
+
]);
|
|
1815
|
+
const items = [];
|
|
1816
|
+
const branchStatus = {};
|
|
1817
|
+
for (const branch of branches) {
|
|
1818
|
+
branchStatus[branch.name] = branch.status;
|
|
1819
|
+
if (branch.status !== "ok") {
|
|
1820
|
+
if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
if (branch.name === "project_rules") {
|
|
1824
|
+
items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
const records = branch.value.memories || [];
|
|
1828
|
+
items.push(...records.map((memory, index) => ({
|
|
1829
|
+
id: String(memory.id || `${branch.name}_${index}`),
|
|
1830
|
+
content: String(memory.content || ""),
|
|
1831
|
+
type: "memory",
|
|
1832
|
+
score: 0.4,
|
|
1833
|
+
sourceQuery: "bootstrap",
|
|
1834
|
+
pass: "bootstrap",
|
|
1835
|
+
metadata: memory
|
|
1836
|
+
})).filter((item) => item.content));
|
|
1837
|
+
}
|
|
1838
|
+
const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
|
|
1839
|
+
const ranked = reranked.items.slice(0, this.broadTopK * 2);
|
|
1840
|
+
const prepared = {
|
|
1841
|
+
scope,
|
|
1842
|
+
retrieval: {
|
|
1843
|
+
primaryQuery: "bootstrap",
|
|
1844
|
+
taskFrameQuery: null,
|
|
1845
|
+
warnings,
|
|
1846
|
+
degraded: warnings.length > 0,
|
|
1847
|
+
degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
|
|
1848
|
+
durationMs: Date.now() - startedAt,
|
|
1849
|
+
targetBudgetMs: this.targetRetrievalMs,
|
|
1850
|
+
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1851
|
+
branchStatus,
|
|
1852
|
+
focusedScopeApplied: false,
|
|
1853
|
+
focusedSourceIds: [],
|
|
1854
|
+
focusedFileHints: [],
|
|
1855
|
+
clientScoped: false,
|
|
1856
|
+
fallbackUsed: false,
|
|
1857
|
+
droppedBelowFloor: 0,
|
|
1858
|
+
dedupedCount: reranked.dedupedCount
|
|
1859
|
+
},
|
|
1860
|
+
context: this.buildContext(ranked),
|
|
1861
|
+
items: ranked
|
|
1862
|
+
};
|
|
1863
|
+
this.lastPreparedTurn = prepared.retrieval;
|
|
1864
|
+
return prepared;
|
|
1865
|
+
}
|
|
1866
|
+
async beforeTurn(input, context = {}) {
|
|
1867
|
+
this.currentTurn += 1;
|
|
1868
|
+
this.pushTouchedFiles(input.touchedFiles);
|
|
1869
|
+
this.refreshTaskSummary(input.taskSummary);
|
|
1870
|
+
const { scope, warning } = await this.resolveScope(context);
|
|
1871
|
+
const primaryQuery = compactWhitespace(input.userMessage);
|
|
1872
|
+
const taskFrameQuery = this.makeTaskFrameQuery(input);
|
|
1873
|
+
const focusedScope = this.focusedScope(input);
|
|
1874
|
+
const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
|
|
1875
|
+
const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
|
|
1876
|
+
const warnings = warning ? [warning] : [];
|
|
1877
|
+
const startedAt = Date.now();
|
|
1878
|
+
const branchStatus = {};
|
|
1879
|
+
const collectFromBranches = (branches, pass) => {
|
|
1880
|
+
const collected = [];
|
|
1881
|
+
let okCount = 0;
|
|
1882
|
+
for (const branch of branches) {
|
|
1883
|
+
branchStatus[branch.name] = branch.status;
|
|
1884
|
+
if (branch.status !== "ok") {
|
|
1885
|
+
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
okCount += 1;
|
|
1889
|
+
if (branch.name.startsWith("context")) {
|
|
1890
|
+
collected.push(...this.contextItems(
|
|
1891
|
+
branch.value,
|
|
1892
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1893
|
+
pass
|
|
1894
|
+
));
|
|
1895
|
+
} else {
|
|
1896
|
+
const memoryItems = this.memoryItems(
|
|
1897
|
+
branch.value,
|
|
1898
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1899
|
+
pass
|
|
1900
|
+
);
|
|
1901
|
+
collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
return { collected, okCount };
|
|
1905
|
+
};
|
|
1906
|
+
const focusedBranches = await Promise.all([
|
|
1907
|
+
this.runBranch("context_primary_focused", () => this.args.adapter.query({
|
|
1908
|
+
project: scope.project,
|
|
1909
|
+
query: primaryQuery,
|
|
1910
|
+
top_k: this.focusedTopK,
|
|
1911
|
+
include_memories: false,
|
|
1912
|
+
user_id: scope.userId,
|
|
1913
|
+
session_id: scope.sessionId,
|
|
1914
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1915
|
+
metadata_filter: focusedMetadataFilter,
|
|
1916
|
+
max_tokens: this.maxTokens,
|
|
1917
|
+
compress: true,
|
|
1918
|
+
compression_strategy: "adaptive"
|
|
1919
|
+
})),
|
|
1920
|
+
this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
|
|
1921
|
+
project: scope.project,
|
|
1922
|
+
query: primaryQuery,
|
|
1923
|
+
user_id: scope.userId,
|
|
1924
|
+
session_id: scope.sessionId,
|
|
1925
|
+
top_k: this.focusedTopK,
|
|
1926
|
+
include_pending: true,
|
|
1927
|
+
profile: "balanced"
|
|
1928
|
+
})),
|
|
1929
|
+
taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
|
|
1930
|
+
project: scope.project,
|
|
1931
|
+
query: taskFrameQuery,
|
|
1932
|
+
top_k: this.focusedTopK,
|
|
1933
|
+
include_memories: false,
|
|
1934
|
+
user_id: scope.userId,
|
|
1935
|
+
session_id: scope.sessionId,
|
|
1936
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1937
|
+
metadata_filter: focusedMetadataFilter,
|
|
1938
|
+
max_tokens: this.maxTokens,
|
|
1939
|
+
compress: true,
|
|
1940
|
+
compression_strategy: "adaptive"
|
|
1941
|
+
})) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
|
|
1942
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
|
|
1943
|
+
project: scope.project,
|
|
1944
|
+
query: taskFrameQuery,
|
|
1945
|
+
user_id: scope.userId,
|
|
1946
|
+
session_id: scope.sessionId,
|
|
1947
|
+
top_k: this.focusedTopK,
|
|
1948
|
+
include_pending: true,
|
|
1949
|
+
profile: "balanced"
|
|
1950
|
+
})) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
|
|
1951
|
+
]);
|
|
1952
|
+
const focusedCollected = collectFromBranches(focusedBranches, "focused");
|
|
1953
|
+
const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
|
|
1954
|
+
const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
|
|
1955
|
+
let allCollected = [...focusedFloored.items];
|
|
1956
|
+
let totalOkCount = focusedCollected.okCount;
|
|
1957
|
+
let dedupedCount = focusedRanked.dedupedCount;
|
|
1958
|
+
let droppedBelowFloor = focusedFloored.dropped;
|
|
1959
|
+
const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
|
|
1960
|
+
const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
|
|
1961
|
+
if (focusedScopeApplied) {
|
|
1962
|
+
this.sourceScopedTurns += 1;
|
|
1963
|
+
}
|
|
1964
|
+
if (!fallbackUsed) {
|
|
1965
|
+
this.focusedPassHits += 1;
|
|
1966
|
+
}
|
|
1967
|
+
const broadBranches = fallbackUsed ? await Promise.all([
|
|
1968
|
+
this.runBranch("context_primary_broad", () => this.args.adapter.query({
|
|
1969
|
+
project: scope.project,
|
|
1970
|
+
query: primaryQuery,
|
|
1971
|
+
top_k: this.broadTopK,
|
|
1972
|
+
include_memories: false,
|
|
1973
|
+
user_id: scope.userId,
|
|
1974
|
+
session_id: scope.sessionId,
|
|
1975
|
+
max_tokens: this.maxTokens,
|
|
1976
|
+
compress: true,
|
|
1977
|
+
compression_strategy: "adaptive"
|
|
1978
|
+
})),
|
|
1979
|
+
this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
|
|
1980
|
+
project: scope.project,
|
|
1981
|
+
query: primaryQuery,
|
|
1982
|
+
user_id: scope.userId,
|
|
1983
|
+
session_id: scope.sessionId,
|
|
1984
|
+
top_k: this.broadTopK,
|
|
1985
|
+
include_pending: true,
|
|
1986
|
+
profile: "balanced"
|
|
1987
|
+
})),
|
|
1988
|
+
taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
|
|
1989
|
+
project: scope.project,
|
|
1990
|
+
query: taskFrameQuery,
|
|
1991
|
+
top_k: this.broadTopK,
|
|
1992
|
+
include_memories: false,
|
|
1993
|
+
user_id: scope.userId,
|
|
1994
|
+
session_id: scope.sessionId,
|
|
1995
|
+
max_tokens: this.maxTokens,
|
|
1996
|
+
compress: true,
|
|
1997
|
+
compression_strategy: "adaptive"
|
|
1998
|
+
})) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
|
|
1999
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
|
|
2000
|
+
project: scope.project,
|
|
2001
|
+
query: taskFrameQuery,
|
|
2002
|
+
user_id: scope.userId,
|
|
2003
|
+
session_id: scope.sessionId,
|
|
2004
|
+
top_k: this.broadTopK,
|
|
2005
|
+
include_pending: true,
|
|
2006
|
+
profile: "balanced"
|
|
2007
|
+
})) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
|
|
2008
|
+
]) : [
|
|
2009
|
+
{ name: "context_primary_broad", status: "skipped", durationMs: 0 },
|
|
2010
|
+
{ name: "memory_primary_broad", status: "skipped", durationMs: 0 },
|
|
2011
|
+
{ name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
|
|
2012
|
+
{ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
|
|
2013
|
+
];
|
|
2014
|
+
const broadCollected = collectFromBranches(broadBranches, "broad");
|
|
2015
|
+
totalOkCount += broadCollected.okCount;
|
|
2016
|
+
if (fallbackUsed) {
|
|
2017
|
+
this.fallbackTriggers += 1;
|
|
2018
|
+
this.broadScopedTurns += 1;
|
|
2019
|
+
allCollected = [...allCollected, ...broadCollected.collected];
|
|
2020
|
+
}
|
|
2021
|
+
const ranked = this.rerank(allCollected, focusedScope);
|
|
2022
|
+
dedupedCount += ranked.dedupedCount;
|
|
2023
|
+
const floored = this.applyRelevanceFloor(ranked.items);
|
|
2024
|
+
droppedBelowFloor += floored.dropped;
|
|
2025
|
+
this.floorDroppedCount += droppedBelowFloor;
|
|
2026
|
+
this.droppedCount += droppedBelowFloor;
|
|
2027
|
+
const finalItems = floored.items.slice(0, this.broadTopK);
|
|
2028
|
+
this.injectedItemCount += finalItems.length;
|
|
2029
|
+
this.totalTurns += 1;
|
|
2030
|
+
const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
|
|
2031
|
+
for (const branch of [...focusedBranches, ...broadBranches]) {
|
|
2032
|
+
branchStatus[branch.name] = branch.status;
|
|
2033
|
+
}
|
|
2034
|
+
const prepared = {
|
|
2035
|
+
scope,
|
|
2036
|
+
retrieval: {
|
|
2037
|
+
primaryQuery,
|
|
2038
|
+
taskFrameQuery,
|
|
2039
|
+
warnings,
|
|
2040
|
+
degraded: totalOkCount < executedBranches.length,
|
|
2041
|
+
degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
|
|
2042
|
+
durationMs: Date.now() - startedAt,
|
|
2043
|
+
targetBudgetMs: this.targetRetrievalMs,
|
|
2044
|
+
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
2045
|
+
branchStatus,
|
|
2046
|
+
focusedScopeApplied,
|
|
2047
|
+
focusedSourceIds: focusedScope.sourceIds,
|
|
2048
|
+
focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
|
|
2049
|
+
clientScoped: Boolean(focusedScope.clientName),
|
|
2050
|
+
fallbackUsed,
|
|
2051
|
+
droppedBelowFloor,
|
|
2052
|
+
dedupedCount
|
|
2053
|
+
},
|
|
2054
|
+
context: this.buildContext(finalItems),
|
|
2055
|
+
items: finalItems
|
|
2056
|
+
};
|
|
2057
|
+
this.lastPreparedTurn = prepared.retrieval;
|
|
2058
|
+
return prepared;
|
|
2059
|
+
}
|
|
2060
|
+
async recordWork(event, context = {}) {
|
|
2061
|
+
const normalized = {
|
|
2062
|
+
...event,
|
|
2063
|
+
salience: event.salience || defaultSalience(event.kind, event.success),
|
|
2064
|
+
timestamp: event.timestamp || nowIso()
|
|
2065
|
+
};
|
|
2066
|
+
this.pushTouchedFiles(normalized.filePaths);
|
|
2067
|
+
this.pushWorkEvent(normalized);
|
|
2068
|
+
if (normalized.salience === "low") {
|
|
2069
|
+
this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
|
|
2070
|
+
return { success: true, buffered: true };
|
|
2071
|
+
}
|
|
2072
|
+
const { scope } = await this.resolveScope(context);
|
|
2073
|
+
return this.args.adapter.addMemory({
|
|
2074
|
+
project: scope.project,
|
|
2075
|
+
user_id: scope.userId,
|
|
2076
|
+
session_id: scope.sessionId,
|
|
2077
|
+
content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
|
|
2078
|
+
memory_type: toMemoryType(normalized.kind),
|
|
2079
|
+
event_date: normalized.timestamp,
|
|
2080
|
+
write_mode: "async",
|
|
2081
|
+
metadata: {
|
|
2082
|
+
runtime_auto: true,
|
|
2083
|
+
client_name: this.clientName,
|
|
2084
|
+
work_event_kind: normalized.kind,
|
|
2085
|
+
salience: normalized.salience,
|
|
2086
|
+
file_paths: normalized.filePaths || [],
|
|
2087
|
+
tool_name: normalized.toolName,
|
|
2088
|
+
success: normalized.success
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
async afterTurn(input, context = {}) {
|
|
2093
|
+
this.pushTouchedFiles(input.touchedFiles);
|
|
2094
|
+
const { scope } = await this.resolveScope(context);
|
|
2095
|
+
const result = await this.args.adapter.ingestSession({
|
|
2096
|
+
project: scope.project,
|
|
2097
|
+
session_id: scope.sessionId,
|
|
2098
|
+
user_id: scope.userId,
|
|
2099
|
+
messages: [
|
|
2100
|
+
{ role: "user", content: input.userMessage, timestamp: nowIso() },
|
|
2101
|
+
{ role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
|
|
2102
|
+
],
|
|
2103
|
+
write_mode: "async"
|
|
2104
|
+
});
|
|
2105
|
+
this.mergedCount += result.memories_invalidated || 0;
|
|
2106
|
+
return {
|
|
2107
|
+
success: Boolean(result.success),
|
|
2108
|
+
sessionIngested: true,
|
|
2109
|
+
memoriesCreated: result.memories_created || 0,
|
|
2110
|
+
relationsCreated: result.relations_created || 0,
|
|
2111
|
+
invalidatedCount: result.memories_invalidated || 0,
|
|
2112
|
+
mergedCount: result.memories_invalidated || 0,
|
|
2113
|
+
droppedCount: 0,
|
|
2114
|
+
warnings: result.errors || []
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
async flush(reason = "manual", context = {}) {
|
|
2118
|
+
if (this.bufferedLowSalience.length > 0) {
|
|
2119
|
+
const { scope } = await this.resolveScope(context);
|
|
2120
|
+
const summary = summarizeLowSalience(this.bufferedLowSalience);
|
|
2121
|
+
await this.args.adapter.addMemory({
|
|
2122
|
+
project: scope.project,
|
|
2123
|
+
user_id: scope.userId,
|
|
2124
|
+
session_id: scope.sessionId,
|
|
2125
|
+
content: summary,
|
|
2126
|
+
memory_type: "event",
|
|
2127
|
+
write_mode: "async",
|
|
2128
|
+
metadata: {
|
|
2129
|
+
runtime_auto: true,
|
|
2130
|
+
client_name: this.clientName,
|
|
2131
|
+
flush_reason: reason,
|
|
2132
|
+
salience: "low",
|
|
2133
|
+
summarized_count: this.bufferedLowSalience.length
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
this.bufferedLowSalience = [];
|
|
2137
|
+
}
|
|
2138
|
+
await this.args.adapter.flushQueue();
|
|
2139
|
+
return this.status();
|
|
2140
|
+
}
|
|
2141
|
+
status() {
|
|
2142
|
+
return {
|
|
2143
|
+
clientName: this.clientName,
|
|
2144
|
+
scope: this.lastScope,
|
|
2145
|
+
queue: this.args.adapter.queueStatus(),
|
|
2146
|
+
retrieval: this.lastPreparedTurn,
|
|
2147
|
+
counters: {
|
|
2148
|
+
mergedCount: this.mergedCount,
|
|
2149
|
+
droppedCount: this.droppedCount,
|
|
2150
|
+
bufferedLowSalience: this.bufferedLowSalience.length,
|
|
2151
|
+
focusedPassHits: this.focusedPassHits,
|
|
2152
|
+
fallbackTriggers: this.fallbackTriggers,
|
|
2153
|
+
floorDroppedCount: this.floorDroppedCount,
|
|
2154
|
+
injectedItemCount: this.injectedItemCount,
|
|
2155
|
+
sourceScopedTurns: this.sourceScopedTurns,
|
|
2156
|
+
broadScopedTurns: this.broadScopedTurns,
|
|
2157
|
+
totalTurns: this.totalTurns
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
// ../src/sdk/whisper.ts
|
|
2164
|
+
var PROJECT_CACHE_TTL_MS = 3e4;
|
|
2165
|
+
function isLikelyProjectId(projectRef) {
|
|
2166
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
|
|
2167
|
+
}
|
|
2168
|
+
var WhisperClient = class _WhisperClient {
|
|
2169
|
+
constructor(config) {
|
|
2170
|
+
this.config = config;
|
|
2171
|
+
this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
|
|
2172
|
+
this.runtimeClient = new RuntimeClient(
|
|
2173
|
+
{
|
|
2174
|
+
apiKey: config.apiKey,
|
|
2175
|
+
baseUrl: config.baseUrl,
|
|
2176
|
+
compatMode: config.compatMode || "fallback",
|
|
2177
|
+
timeouts: config.timeouts,
|
|
2178
|
+
retryPolicy: config.retryPolicy,
|
|
2179
|
+
fetchImpl: config.fetch
|
|
2180
|
+
},
|
|
2181
|
+
this.diagnosticsStore
|
|
2182
|
+
);
|
|
2183
|
+
this.searchCache = new SearchResponseCache(
|
|
2184
|
+
config.cache?.ttlMs ?? 7e3,
|
|
2185
|
+
config.cache?.capacity ?? 500
|
|
2186
|
+
);
|
|
2187
|
+
const queueStore = this.createQueueStore(config);
|
|
2188
|
+
this.writeQueue = new WriteQueue({
|
|
2189
|
+
store: queueStore,
|
|
2190
|
+
maxBatchSize: config.queue?.maxBatchSize ?? 50,
|
|
2191
|
+
flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
|
|
2192
|
+
maxAttempts: config.queue?.maxAttempts ?? 2,
|
|
2193
|
+
flushHandler: async (items) => {
|
|
2194
|
+
if (items.length === 0) return;
|
|
2195
|
+
const project = items[0].project;
|
|
2196
|
+
const memories = items.map((item) => ({
|
|
2197
|
+
...item.payload,
|
|
2198
|
+
user_id: item.payload.user_id ?? item.userId,
|
|
2199
|
+
session_id: item.payload.session_id ?? item.sessionId,
|
|
2200
|
+
metadata: {
|
|
2201
|
+
...item.payload.metadata || {},
|
|
2202
|
+
event_id: item.eventId,
|
|
2203
|
+
queued_at: item.createdAt
|
|
2204
|
+
}
|
|
2205
|
+
}));
|
|
2206
|
+
try {
|
|
2207
|
+
await this.runtimeClient.request({
|
|
2208
|
+
endpoint: "/v1/memory/bulk",
|
|
2209
|
+
method: "POST",
|
|
2210
|
+
operation: "bulk",
|
|
2211
|
+
body: {
|
|
2212
|
+
project,
|
|
2213
|
+
write_mode: "async",
|
|
2214
|
+
memories
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
|
|
2219
|
+
throw error;
|
|
2220
|
+
}
|
|
2221
|
+
await Promise.all(
|
|
2222
|
+
memories.map(async (memory) => {
|
|
2223
|
+
try {
|
|
2224
|
+
await this.runtimeClient.request({
|
|
2225
|
+
endpoint: "/v1/memory",
|
|
2226
|
+
method: "POST",
|
|
2227
|
+
operation: "writeAck",
|
|
2228
|
+
body: {
|
|
2229
|
+
project,
|
|
2230
|
+
...memory,
|
|
2231
|
+
write_mode: "sync"
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
} catch (fallbackError) {
|
|
2235
|
+
if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
|
|
2236
|
+
throw fallbackError;
|
|
2237
|
+
}
|
|
2238
|
+
await this.runtimeClient.request({
|
|
2239
|
+
endpoint: "/v1/memories",
|
|
2240
|
+
method: "POST",
|
|
2241
|
+
operation: "writeAck",
|
|
2242
|
+
body: {
|
|
2243
|
+
project,
|
|
2244
|
+
...memory,
|
|
2245
|
+
memory_type: memory.memory_type === "event" ? "episodic" : memory.memory_type
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
})
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
if (config.queue?.enabled !== false) {
|
|
2255
|
+
void this.writeQueue.start();
|
|
2256
|
+
}
|
|
2257
|
+
this.memoryModule = new MemoryModule(
|
|
2258
|
+
this.runtimeClient,
|
|
2259
|
+
this.searchCache,
|
|
2260
|
+
this.writeQueue,
|
|
2261
|
+
{
|
|
2262
|
+
defaultProject: config.project,
|
|
2263
|
+
cacheEnabled: config.cache?.enabled !== false,
|
|
2264
|
+
queueEnabled: config.queue?.enabled !== false
|
|
2265
|
+
}
|
|
2266
|
+
);
|
|
2267
|
+
this.sessionModule = new SessionModule(this.memoryModule, config.project);
|
|
2268
|
+
this.profileModule = new ProfileModule(this.memoryModule);
|
|
2269
|
+
this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
|
|
2270
|
+
this.diagnostics = {
|
|
2271
|
+
getLast: (limit) => this.diagnosticsStore.getLast(limit),
|
|
2272
|
+
subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
|
|
2273
|
+
snapshot: () => this.diagnosticsStore.snapshot()
|
|
2274
|
+
};
|
|
2275
|
+
this.queue = {
|
|
2276
|
+
flush: () => this.writeQueue.flush(),
|
|
2277
|
+
status: () => this.writeQueue.status()
|
|
2278
|
+
};
|
|
2279
|
+
this.memory = {
|
|
2280
|
+
add: (params) => this.memoryModule.add(params),
|
|
2281
|
+
addBulk: (params) => this.memoryModule.addBulk(params),
|
|
2282
|
+
search: (params) => this.memoryModule.search(params),
|
|
2283
|
+
get: (memoryId) => this.memoryModule.get(memoryId),
|
|
2284
|
+
getUserProfile: (params) => this.memoryModule.getUserProfile(params),
|
|
2285
|
+
getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
|
|
2286
|
+
update: (memoryId, params) => this.memoryModule.update(memoryId, params),
|
|
2287
|
+
delete: (memoryId) => this.memoryModule.delete(memoryId),
|
|
2288
|
+
flag: (params) => this.memoryModule.flag(params)
|
|
2289
|
+
};
|
|
2290
|
+
this.session = {
|
|
2291
|
+
start: (params) => this.sessionModule.start(params),
|
|
2292
|
+
event: (params) => this.sessionModule.event(params),
|
|
2293
|
+
suspend: (params) => this.sessionModule.suspend(params),
|
|
2294
|
+
resume: (params) => this.sessionModule.resume(params),
|
|
2295
|
+
end: (params) => this.sessionModule.end(params)
|
|
2296
|
+
};
|
|
2297
|
+
this.profile = {
|
|
2298
|
+
getUserProfile: (params) => this.profileModule.getUserProfile(params),
|
|
2299
|
+
getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
|
|
2300
|
+
};
|
|
2301
|
+
this.analytics = {
|
|
2302
|
+
diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
|
|
2303
|
+
queueStatus: () => this.analyticsModule.queueStatus()
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
diagnostics;
|
|
2307
|
+
queue;
|
|
2308
|
+
memory;
|
|
2309
|
+
session;
|
|
2310
|
+
profile;
|
|
2311
|
+
analytics;
|
|
2312
|
+
runtimeClient;
|
|
2313
|
+
diagnosticsStore;
|
|
2314
|
+
searchCache;
|
|
2315
|
+
writeQueue;
|
|
2316
|
+
memoryModule;
|
|
2317
|
+
sessionModule;
|
|
2318
|
+
profileModule;
|
|
2319
|
+
analyticsModule;
|
|
2320
|
+
projectRefToId = /* @__PURE__ */ new Map();
|
|
2321
|
+
projectCache = [];
|
|
2322
|
+
projectCacheExpiresAt = 0;
|
|
2323
|
+
static fromEnv(overrides = {}) {
|
|
2324
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
2325
|
+
const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
|
|
2326
|
+
if (!apiKey) {
|
|
2327
|
+
throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
|
|
2328
|
+
}
|
|
2329
|
+
return new _WhisperClient({
|
|
2330
|
+
apiKey,
|
|
2331
|
+
baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
|
|
2332
|
+
project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
|
|
2333
|
+
...overrides
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
createQueueStore(config) {
|
|
2337
|
+
const persistence = config.queue?.persistence || this.defaultQueuePersistence();
|
|
2338
|
+
if (persistence === "storage") {
|
|
2339
|
+
return createStorageQueueStore();
|
|
2340
|
+
}
|
|
2341
|
+
if (persistence === "file") {
|
|
2342
|
+
const filePath = config.queue?.filePath || this.defaultQueueFilePath();
|
|
2343
|
+
if (filePath) {
|
|
2344
|
+
return createFileQueueStore(filePath);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return new InMemoryQueueStore();
|
|
2348
|
+
}
|
|
2349
|
+
defaultQueuePersistence() {
|
|
2350
|
+
const maybeWindow = globalThis.window;
|
|
2351
|
+
if (maybeWindow && typeof maybeWindow === "object") {
|
|
2352
|
+
const maybeStorage = globalThis.localStorage;
|
|
2353
|
+
return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
|
|
2354
|
+
}
|
|
2355
|
+
return "file";
|
|
2356
|
+
}
|
|
2357
|
+
defaultQueueFilePath() {
|
|
2358
|
+
if (typeof process === "undefined") return void 0;
|
|
2359
|
+
const path = process.env.WHISPER_QUEUE_FILE_PATH;
|
|
2360
|
+
if (path) return path;
|
|
2361
|
+
const home = process.env.USERPROFILE || process.env.HOME;
|
|
2362
|
+
if (!home) return void 0;
|
|
2363
|
+
const normalizedHome = home.replace(/[\\\/]+$/, "");
|
|
2364
|
+
return `${normalizedHome}/.whisper/sdk/queue.json`;
|
|
2365
|
+
}
|
|
2366
|
+
getRequiredProject(project) {
|
|
2367
|
+
const resolved = project || this.config.project;
|
|
2368
|
+
if (!resolved) {
|
|
2369
|
+
throw new RuntimeClientError({
|
|
2370
|
+
code: "MISSING_PROJECT",
|
|
2371
|
+
message: "Project is required",
|
|
2372
|
+
retryable: false
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
return resolved;
|
|
2376
|
+
}
|
|
2377
|
+
async refreshProjectCache(force = false) {
|
|
2378
|
+
if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
|
|
2379
|
+
return this.projectCache;
|
|
2380
|
+
}
|
|
2381
|
+
const response = await this.runtimeClient.request({
|
|
2382
|
+
endpoint: "/v1/projects",
|
|
2383
|
+
method: "GET",
|
|
2384
|
+
operation: "get",
|
|
2385
|
+
idempotent: true
|
|
272
2386
|
});
|
|
273
|
-
|
|
274
|
-
|
|
2387
|
+
this.projectRefToId.clear();
|
|
2388
|
+
this.projectCache = response.data?.projects || [];
|
|
2389
|
+
for (const project of this.projectCache) {
|
|
2390
|
+
this.projectRefToId.set(project.id, project.id);
|
|
2391
|
+
this.projectRefToId.set(project.slug, project.id);
|
|
2392
|
+
this.projectRefToId.set(project.name, project.id);
|
|
275
2393
|
}
|
|
276
|
-
|
|
2394
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2395
|
+
return this.projectCache;
|
|
277
2396
|
}
|
|
278
|
-
async
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
291
|
-
try {
|
|
292
|
-
const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
|
|
293
|
-
const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
|
|
294
|
-
method,
|
|
295
|
-
signal: controller.signal,
|
|
296
|
-
keepalive: method !== "GET",
|
|
297
|
-
headers: {
|
|
298
|
-
"Content-Type": "application/json",
|
|
299
|
-
Authorization: this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`,
|
|
300
|
-
...attachApiKeyHeader ? { "X-API-Key": this.apiKey.replace(/^Bearer\s+/i, "") } : {},
|
|
301
|
-
"x-trace-id": traceId,
|
|
302
|
-
"x-span-id": spanId,
|
|
303
|
-
"x-sdk-version": this.sdkVersion,
|
|
304
|
-
"x-sdk-runtime": this.runtimeName(),
|
|
305
|
-
...options.headers || {}
|
|
306
|
-
},
|
|
307
|
-
body: method === "GET" || method === "DELETE" ? void 0 : JSON.stringify(options.body || {})
|
|
308
|
-
});
|
|
309
|
-
clearTimeout(timeout);
|
|
310
|
-
let payload = null;
|
|
311
|
-
try {
|
|
312
|
-
payload = await response.json();
|
|
313
|
-
} catch {
|
|
314
|
-
payload = await response.text().catch(() => "");
|
|
315
|
-
}
|
|
316
|
-
const durationMs = Date.now() - startedAt;
|
|
317
|
-
const record = {
|
|
318
|
-
id: randomId("diag"),
|
|
319
|
-
startedAt: new Date(startedAt).toISOString(),
|
|
320
|
-
endedAt: nowIso(),
|
|
321
|
-
traceId,
|
|
322
|
-
spanId,
|
|
323
|
-
operation,
|
|
324
|
-
method,
|
|
325
|
-
endpoint: normalizedEndpoint,
|
|
326
|
-
status: response.status,
|
|
327
|
-
durationMs,
|
|
328
|
-
success: response.ok
|
|
329
|
-
};
|
|
330
|
-
this.diagnostics.add(record);
|
|
331
|
-
if (response.ok) {
|
|
332
|
-
return {
|
|
333
|
-
data: payload,
|
|
334
|
-
status: response.status,
|
|
335
|
-
traceId
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const message = toMessage(payload, response.status, response.statusText);
|
|
339
|
-
const retryable = this.shouldRetryStatus(response.status);
|
|
340
|
-
const error = new RuntimeClientError({
|
|
341
|
-
message,
|
|
342
|
-
status: response.status,
|
|
343
|
-
retryable,
|
|
344
|
-
code: response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED",
|
|
345
|
-
details: payload,
|
|
346
|
-
traceId
|
|
347
|
-
});
|
|
348
|
-
lastError = error;
|
|
349
|
-
if (!retryable || attempt === maxAttempts - 1) {
|
|
350
|
-
throw error;
|
|
351
|
-
}
|
|
352
|
-
} catch (error) {
|
|
353
|
-
clearTimeout(timeout);
|
|
354
|
-
const durationMs = Date.now() - startedAt;
|
|
355
|
-
const isAbort = isObject(error) && error.name === "AbortError";
|
|
356
|
-
const mapped = error instanceof RuntimeClientError ? error : new RuntimeClientError({
|
|
357
|
-
message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
|
|
358
|
-
retryable: this.retryPolicy.retryOnNetworkError ?? true,
|
|
359
|
-
code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
|
|
360
|
-
traceId
|
|
361
|
-
});
|
|
362
|
-
lastError = mapped;
|
|
363
|
-
this.diagnostics.add({
|
|
364
|
-
id: randomId("diag"),
|
|
365
|
-
startedAt: new Date(startedAt).toISOString(),
|
|
366
|
-
endedAt: nowIso(),
|
|
367
|
-
traceId,
|
|
368
|
-
spanId,
|
|
369
|
-
operation,
|
|
370
|
-
method,
|
|
371
|
-
endpoint: normalizedEndpoint,
|
|
372
|
-
durationMs,
|
|
373
|
-
success: false,
|
|
374
|
-
errorCode: mapped.code,
|
|
375
|
-
errorMessage: mapped.message
|
|
376
|
-
});
|
|
377
|
-
if (!mapped.retryable || attempt === maxAttempts - 1) {
|
|
378
|
-
throw mapped;
|
|
379
|
-
}
|
|
2397
|
+
async fetchResolvedProject(projectRef) {
|
|
2398
|
+
try {
|
|
2399
|
+
const response = await this.runtimeClient.request({
|
|
2400
|
+
endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
|
|
2401
|
+
method: "GET",
|
|
2402
|
+
operation: "get",
|
|
2403
|
+
idempotent: true
|
|
2404
|
+
});
|
|
2405
|
+
return response.data?.resolved || null;
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
if (error instanceof RuntimeClientError && error.status === 404) {
|
|
2408
|
+
return null;
|
|
380
2409
|
}
|
|
381
|
-
|
|
2410
|
+
throw error;
|
|
382
2411
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
2412
|
+
}
|
|
2413
|
+
async resolveProject(projectRef) {
|
|
2414
|
+
const resolvedRef = this.getRequiredProject(projectRef);
|
|
2415
|
+
const cachedProjects = await this.refreshProjectCache(false);
|
|
2416
|
+
const cachedProject = cachedProjects.find(
|
|
2417
|
+
(project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
|
|
2418
|
+
);
|
|
2419
|
+
if (cachedProject) {
|
|
2420
|
+
return cachedProject;
|
|
2421
|
+
}
|
|
2422
|
+
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2423
|
+
if (resolvedProject) {
|
|
2424
|
+
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2425
|
+
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2426
|
+
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2427
|
+
this.projectCache = [
|
|
2428
|
+
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2429
|
+
resolvedProject
|
|
2430
|
+
];
|
|
2431
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2432
|
+
return resolvedProject;
|
|
2433
|
+
}
|
|
2434
|
+
if (isLikelyProjectId(resolvedRef)) {
|
|
2435
|
+
return {
|
|
2436
|
+
id: resolvedRef,
|
|
2437
|
+
orgId: "",
|
|
2438
|
+
name: resolvedRef,
|
|
2439
|
+
slug: resolvedRef,
|
|
2440
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2441
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
throw new RuntimeClientError({
|
|
2445
|
+
code: "PROJECT_NOT_FOUND",
|
|
2446
|
+
message: `Project '${resolvedRef}' not found`,
|
|
2447
|
+
retryable: false
|
|
387
2448
|
});
|
|
388
2449
|
}
|
|
2450
|
+
async query(params) {
|
|
2451
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2452
|
+
const response = await this.runtimeClient.request({
|
|
2453
|
+
endpoint: "/v1/context/query",
|
|
2454
|
+
method: "POST",
|
|
2455
|
+
operation: "search",
|
|
2456
|
+
body: {
|
|
2457
|
+
...params,
|
|
2458
|
+
project
|
|
2459
|
+
},
|
|
2460
|
+
idempotent: true
|
|
2461
|
+
});
|
|
2462
|
+
return response.data;
|
|
2463
|
+
}
|
|
2464
|
+
async ingestSession(params) {
|
|
2465
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2466
|
+
const response = await this.runtimeClient.request({
|
|
2467
|
+
endpoint: "/v1/memory/ingest/session",
|
|
2468
|
+
method: "POST",
|
|
2469
|
+
operation: "session",
|
|
2470
|
+
body: {
|
|
2471
|
+
...params,
|
|
2472
|
+
project
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
return response.data;
|
|
2476
|
+
}
|
|
2477
|
+
createAgentRuntime(options = {}) {
|
|
2478
|
+
const baseContext = {
|
|
2479
|
+
workspacePath: options.workspacePath,
|
|
2480
|
+
project: options.project || this.config.project,
|
|
2481
|
+
userId: options.userId,
|
|
2482
|
+
sessionId: options.sessionId,
|
|
2483
|
+
traceId: options.traceId,
|
|
2484
|
+
clientName: options.clientName
|
|
2485
|
+
};
|
|
2486
|
+
return new WhisperAgentRuntime({
|
|
2487
|
+
baseContext,
|
|
2488
|
+
options,
|
|
2489
|
+
adapter: {
|
|
2490
|
+
resolveProject: (project) => this.resolveProject(project),
|
|
2491
|
+
query: (params) => this.query(params),
|
|
2492
|
+
ingestSession: (params) => this.ingestSession(params),
|
|
2493
|
+
getSessionMemories: (params) => this.memory.getSessionMemories(params),
|
|
2494
|
+
getUserProfile: (params) => this.memory.getUserProfile(params),
|
|
2495
|
+
searchMemories: (params) => this.memory.search(params),
|
|
2496
|
+
addMemory: (params) => this.memory.add(params),
|
|
2497
|
+
queueStatus: () => this.queue.status(),
|
|
2498
|
+
flushQueue: () => this.queue.flush()
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
withRunContext(context) {
|
|
2503
|
+
const base = this;
|
|
2504
|
+
return {
|
|
2505
|
+
memory: {
|
|
2506
|
+
add: (params) => base.memory.add({
|
|
2507
|
+
...params,
|
|
2508
|
+
project: params.project || context.project || base.config.project,
|
|
2509
|
+
user_id: params.user_id || context.userId,
|
|
2510
|
+
session_id: params.session_id || context.sessionId
|
|
2511
|
+
}),
|
|
2512
|
+
search: (params) => base.memory.search({
|
|
2513
|
+
...params,
|
|
2514
|
+
project: params.project || context.project || base.config.project,
|
|
2515
|
+
user_id: params.user_id || context.userId,
|
|
2516
|
+
session_id: params.session_id || context.sessionId
|
|
2517
|
+
})
|
|
2518
|
+
},
|
|
2519
|
+
session: {
|
|
2520
|
+
event: (params) => base.session.event({
|
|
2521
|
+
...params,
|
|
2522
|
+
sessionId: params.sessionId || context.sessionId || ""
|
|
2523
|
+
})
|
|
2524
|
+
},
|
|
2525
|
+
queue: base.queue,
|
|
2526
|
+
diagnostics: base.diagnostics
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
async shutdown() {
|
|
2530
|
+
await this.writeQueue.stop();
|
|
2531
|
+
}
|
|
389
2532
|
};
|
|
390
2533
|
|
|
391
2534
|
// ../src/sdk/index.ts
|
|
@@ -407,7 +2550,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
|
|
|
407
2550
|
var DEFAULT_BASE_DELAY_MS = 250;
|
|
408
2551
|
var DEFAULT_MAX_DELAY_MS = 2e3;
|
|
409
2552
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
410
|
-
var
|
|
2553
|
+
var PROJECT_CACHE_TTL_MS2 = 3e4;
|
|
411
2554
|
var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
|
|
412
2555
|
function warnDeprecatedOnce(key, message) {
|
|
413
2556
|
if (DEPRECATION_WARNINGS.has(key)) return;
|
|
@@ -416,7 +2559,7 @@ function warnDeprecatedOnce(key, message) {
|
|
|
416
2559
|
console.warn(message);
|
|
417
2560
|
}
|
|
418
2561
|
}
|
|
419
|
-
function
|
|
2562
|
+
function isLikelyProjectId2(projectRef) {
|
|
420
2563
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
|
|
421
2564
|
}
|
|
422
2565
|
function normalizeBaseUrl2(url) {
|
|
@@ -538,7 +2681,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
538
2681
|
}
|
|
539
2682
|
}
|
|
540
2683
|
}
|
|
541
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2684
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
542
2685
|
return this.projectCache;
|
|
543
2686
|
}
|
|
544
2687
|
async fetchResolvedProject(projectRef) {
|
|
@@ -571,10 +2714,10 @@ var WhisperContext = class _WhisperContext {
|
|
|
571
2714
|
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
572
2715
|
resolvedProject
|
|
573
2716
|
];
|
|
574
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2717
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
575
2718
|
return resolvedProject;
|
|
576
2719
|
}
|
|
577
|
-
if (
|
|
2720
|
+
if (isLikelyProjectId2(resolvedRef)) {
|
|
578
2721
|
return {
|
|
579
2722
|
id: resolvedRef,
|
|
580
2723
|
orgId: "",
|
|
@@ -606,7 +2749,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
606
2749
|
message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
|
|
607
2750
|
});
|
|
608
2751
|
}
|
|
609
|
-
if (
|
|
2752
|
+
if (isLikelyProjectId2(projectRef)) {
|
|
610
2753
|
return projectRef;
|
|
611
2754
|
}
|
|
612
2755
|
const resolvedProject = await this.fetchResolvedProject(projectRef);
|
|
@@ -618,7 +2761,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
618
2761
|
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
619
2762
|
resolvedProject
|
|
620
2763
|
];
|
|
621
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2764
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
622
2765
|
return resolvedProject.id;
|
|
623
2766
|
}
|
|
624
2767
|
throw new WhisperError({
|
|
@@ -635,7 +2778,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
635
2778
|
candidates.add(match.id);
|
|
636
2779
|
candidates.add(match.slug);
|
|
637
2780
|
candidates.add(match.name);
|
|
638
|
-
} else if (
|
|
2781
|
+
} else if (isLikelyProjectId2(projectRef)) {
|
|
639
2782
|
const byId = projects.find((p) => p.id === projectRef);
|
|
640
2783
|
if (byId) {
|
|
641
2784
|
candidates.add(byId.slug);
|
|
@@ -796,7 +2939,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
796
2939
|
...this.projectCache.filter((p) => p.id !== project.id),
|
|
797
2940
|
project
|
|
798
2941
|
];
|
|
799
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2942
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
800
2943
|
return project;
|
|
801
2944
|
}
|
|
802
2945
|
async listProjects() {
|
|
@@ -807,7 +2950,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
807
2950
|
this.projectRefToId.set(p.slug, p.id);
|
|
808
2951
|
this.projectRefToId.set(p.name, p.id);
|
|
809
2952
|
}
|
|
810
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2953
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
811
2954
|
return projects;
|
|
812
2955
|
}
|
|
813
2956
|
async getProject(id) {
|
|
@@ -929,7 +3072,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
929
3072
|
async addMemory(params) {
|
|
930
3073
|
const projectRef = this.getRequiredProject(params.project);
|
|
931
3074
|
return this.withProjectRefFallback(projectRef, async (project) => {
|
|
932
|
-
const
|
|
3075
|
+
const toSotaType2 = (memoryType) => {
|
|
933
3076
|
switch (memoryType) {
|
|
934
3077
|
case "episodic":
|
|
935
3078
|
return "event";
|
|
@@ -941,7 +3084,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
941
3084
|
return memoryType;
|
|
942
3085
|
}
|
|
943
3086
|
};
|
|
944
|
-
const
|
|
3087
|
+
const toLegacyType2 = (memoryType) => {
|
|
945
3088
|
switch (memoryType) {
|
|
946
3089
|
case "event":
|
|
947
3090
|
return "episodic";
|
|
@@ -962,7 +3105,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
962
3105
|
body: JSON.stringify({
|
|
963
3106
|
project,
|
|
964
3107
|
content: params.content,
|
|
965
|
-
memory_type:
|
|
3108
|
+
memory_type: toSotaType2(params.memory_type),
|
|
966
3109
|
user_id: params.user_id,
|
|
967
3110
|
session_id: params.session_id,
|
|
968
3111
|
agent_id: params.agent_id,
|
|
@@ -1016,7 +3159,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
1016
3159
|
body: JSON.stringify({
|
|
1017
3160
|
project,
|
|
1018
3161
|
content: params.content,
|
|
1019
|
-
memory_type:
|
|
3162
|
+
memory_type: toLegacyType2(params.memory_type),
|
|
1020
3163
|
user_id: params.user_id,
|
|
1021
3164
|
session_id: params.session_id,
|
|
1022
3165
|
agent_id: params.agent_id,
|
|
@@ -1577,10 +3720,23 @@ function createWhisperMcpClient(options) {
|
|
|
1577
3720
|
...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
|
|
1578
3721
|
});
|
|
1579
3722
|
}
|
|
3723
|
+
function createWhisperMcpRuntimeClient(options) {
|
|
3724
|
+
const apiKey = options?.apiKey ?? API_KEY;
|
|
3725
|
+
if (!apiKey || IS_MANAGEMENT_ONLY) {
|
|
3726
|
+
return null;
|
|
3727
|
+
}
|
|
3728
|
+
return new WhisperClient({
|
|
3729
|
+
apiKey,
|
|
3730
|
+
project: options?.project ?? DEFAULT_PROJECT,
|
|
3731
|
+
...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
1580
3734
|
var whisper = createWhisperMcpClient();
|
|
3735
|
+
var runtimeClient = createWhisperMcpRuntimeClient();
|
|
3736
|
+
var runtimeSessions = /* @__PURE__ */ new Map();
|
|
1581
3737
|
var server = new McpServer({
|
|
1582
3738
|
name: "whisper-context",
|
|
1583
|
-
version: "0.
|
|
3739
|
+
version: "0.3.0"
|
|
1584
3740
|
});
|
|
1585
3741
|
function createMcpServer() {
|
|
1586
3742
|
return server;
|
|
@@ -1642,7 +3798,7 @@ function getWorkspaceIdForPath(path, workspaceId) {
|
|
|
1642
3798
|
const seed = `${path}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12)}`;
|
|
1643
3799
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
1644
3800
|
}
|
|
1645
|
-
function
|
|
3801
|
+
function clamp012(value) {
|
|
1646
3802
|
if (Number.isNaN(value)) return 0;
|
|
1647
3803
|
if (value < 0) return 0;
|
|
1648
3804
|
if (value > 1) return 1;
|
|
@@ -1688,7 +3844,7 @@ function toEvidenceRef(source, workspaceId, methodFallback) {
|
|
|
1688
3844
|
line_start: lineStart,
|
|
1689
3845
|
...lineEnd ? { line_end: lineEnd } : {},
|
|
1690
3846
|
snippet: String(source.content || metadata.snippet || "").slice(0, 500),
|
|
1691
|
-
score:
|
|
3847
|
+
score: clamp012(Number(source.score ?? metadata.score ?? 0)),
|
|
1692
3848
|
retrieval_method: retrievalMethod,
|
|
1693
3849
|
indexed_at: String(metadata.indexed_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
1694
3850
|
...metadata.commit ? { commit: String(metadata.commit) } : {},
|
|
@@ -1739,6 +3895,7 @@ function computeChecksum(value) {
|
|
|
1739
3895
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1740
3896
|
}
|
|
1741
3897
|
var cachedProjectRef = DEFAULT_PROJECT || void 0;
|
|
3898
|
+
var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
|
|
1742
3899
|
async function resolveProjectRef(explicit) {
|
|
1743
3900
|
if (explicit?.trim()) {
|
|
1744
3901
|
const requestedRef = explicit.trim();
|
|
@@ -1762,6 +3919,74 @@ async function resolveProjectRef(explicit) {
|
|
|
1762
3919
|
return void 0;
|
|
1763
3920
|
}
|
|
1764
3921
|
}
|
|
3922
|
+
function defaultMcpUserId() {
|
|
3923
|
+
const explicit = process.env.WHISPER_USER_ID?.trim();
|
|
3924
|
+
if (explicit) return explicit;
|
|
3925
|
+
const seed = `${process.cwd()}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3926
|
+
return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
|
|
3927
|
+
}
|
|
3928
|
+
function resolveMcpScope(params) {
|
|
3929
|
+
return {
|
|
3930
|
+
project: params?.project,
|
|
3931
|
+
userId: params?.user_id?.trim() || defaultMcpUserId(),
|
|
3932
|
+
sessionId: params?.session_id?.trim() || cachedMcpSessionId,
|
|
3933
|
+
workspacePath: process.env.WHISPER_WORKSPACE_PATH || process.cwd()
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
async function prepareAutomaticQuery(params) {
|
|
3937
|
+
if (!runtimeClient) {
|
|
3938
|
+
throw new Error("Whisper runtime client unavailable.");
|
|
3939
|
+
}
|
|
3940
|
+
const scope = resolveMcpScope(params);
|
|
3941
|
+
const key = [
|
|
3942
|
+
params.project || DEFAULT_PROJECT || "",
|
|
3943
|
+
scope.userId,
|
|
3944
|
+
scope.sessionId,
|
|
3945
|
+
scope.workspacePath || process.cwd(),
|
|
3946
|
+
String(params.top_k || 10)
|
|
3947
|
+
].join("|");
|
|
3948
|
+
let runtime = runtimeSessions.get(key);
|
|
3949
|
+
if (!runtime) {
|
|
3950
|
+
runtime = runtimeClient.createAgentRuntime({
|
|
3951
|
+
project: params.project,
|
|
3952
|
+
userId: scope.userId,
|
|
3953
|
+
sessionId: scope.sessionId,
|
|
3954
|
+
workspacePath: scope.workspacePath,
|
|
3955
|
+
topK: params.top_k,
|
|
3956
|
+
clientName: "whisper-mcp"
|
|
3957
|
+
});
|
|
3958
|
+
runtimeSessions.set(key, runtime);
|
|
3959
|
+
}
|
|
3960
|
+
return runtime.beforeTurn({
|
|
3961
|
+
userMessage: params.query
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
function noteAutomaticSourceActivity(params) {
|
|
3965
|
+
if (!runtimeClient) return;
|
|
3966
|
+
const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
|
|
3967
|
+
if (sourceIds.length === 0) return;
|
|
3968
|
+
const scope = resolveMcpScope(params);
|
|
3969
|
+
const key = [
|
|
3970
|
+
params.project || DEFAULT_PROJECT || "",
|
|
3971
|
+
scope.userId,
|
|
3972
|
+
scope.sessionId,
|
|
3973
|
+
scope.workspacePath || process.cwd(),
|
|
3974
|
+
String(params.top_k || 10)
|
|
3975
|
+
].join("|");
|
|
3976
|
+
let runtime = runtimeSessions.get(key);
|
|
3977
|
+
if (!runtime) {
|
|
3978
|
+
runtime = runtimeClient.createAgentRuntime({
|
|
3979
|
+
project: params.project,
|
|
3980
|
+
userId: scope.userId,
|
|
3981
|
+
sessionId: scope.sessionId,
|
|
3982
|
+
workspacePath: scope.workspacePath,
|
|
3983
|
+
topK: params.top_k,
|
|
3984
|
+
clientName: "whisper-mcp"
|
|
3985
|
+
});
|
|
3986
|
+
runtimeSessions.set(key, runtime);
|
|
3987
|
+
}
|
|
3988
|
+
runtime.noteSourceActivity(sourceIds);
|
|
3989
|
+
}
|
|
1765
3990
|
function buildAbstain(args) {
|
|
1766
3991
|
return {
|
|
1767
3992
|
status: "abstained",
|
|
@@ -1857,6 +4082,8 @@ async function queryWithDegradedFallback(params) {
|
|
|
1857
4082
|
top_k: params.top_k,
|
|
1858
4083
|
include_memories: params.include_memories,
|
|
1859
4084
|
include_graph: params.include_graph,
|
|
4085
|
+
user_id: params.user_id,
|
|
4086
|
+
session_id: params.session_id,
|
|
1860
4087
|
hybrid: true,
|
|
1861
4088
|
rerank: true
|
|
1862
4089
|
});
|
|
@@ -1869,6 +4096,8 @@ async function queryWithDegradedFallback(params) {
|
|
|
1869
4096
|
top_k: params.top_k,
|
|
1870
4097
|
include_memories: params.include_memories,
|
|
1871
4098
|
include_graph: false,
|
|
4099
|
+
user_id: params.user_id,
|
|
4100
|
+
session_id: params.session_id,
|
|
1872
4101
|
hybrid: false,
|
|
1873
4102
|
rerank: false,
|
|
1874
4103
|
vector_weight: 0,
|
|
@@ -2583,7 +4812,7 @@ server.tool(
|
|
|
2583
4812
|
query: z.string().describe("What are you looking for?"),
|
|
2584
4813
|
top_k: z.number().optional().default(10).describe("Number of results"),
|
|
2585
4814
|
chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
|
|
2586
|
-
include_memories: z.boolean().optional().
|
|
4815
|
+
include_memories: z.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
|
|
2587
4816
|
include_graph: z.boolean().optional().default(false).describe("Include knowledge graph traversal"),
|
|
2588
4817
|
user_id: z.string().optional().describe("User ID for memory scoping"),
|
|
2589
4818
|
session_id: z.string().optional().describe("Session ID for memory scoping"),
|
|
@@ -2595,20 +4824,85 @@ server.tool(
|
|
|
2595
4824
|
if (!resolvedProject) {
|
|
2596
4825
|
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
|
|
2597
4826
|
}
|
|
4827
|
+
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4828
|
+
if (automaticMode) {
|
|
4829
|
+
try {
|
|
4830
|
+
const prepared = await prepareAutomaticQuery({
|
|
4831
|
+
project: resolvedProject,
|
|
4832
|
+
query,
|
|
4833
|
+
top_k,
|
|
4834
|
+
user_id,
|
|
4835
|
+
session_id
|
|
4836
|
+
});
|
|
4837
|
+
if (!prepared.items.length) {
|
|
4838
|
+
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4839
|
+
}
|
|
4840
|
+
const warnings = prepared.retrieval.warnings.length ? `
|
|
4841
|
+
|
|
4842
|
+
[automatic_runtime]
|
|
4843
|
+
${prepared.retrieval.warnings.join("\n")}` : "";
|
|
4844
|
+
const diagnostics = [
|
|
4845
|
+
`focused_scope=${prepared.retrieval.focusedScopeApplied}`,
|
|
4846
|
+
`fallback_used=${prepared.retrieval.fallbackUsed}`,
|
|
4847
|
+
`deduped=${prepared.retrieval.dedupedCount}`,
|
|
4848
|
+
`dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
|
|
4849
|
+
].join(" ");
|
|
4850
|
+
const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
|
|
4851
|
+
return {
|
|
4852
|
+
content: [{
|
|
4853
|
+
type: "text",
|
|
4854
|
+
text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}, ${diagnostics}):
|
|
4855
|
+
|
|
4856
|
+
${prepared.context}${warnings}`
|
|
4857
|
+
}]
|
|
4858
|
+
};
|
|
4859
|
+
} catch (error) {
|
|
4860
|
+
const automaticWarning = `Automatic runtime unavailable: ${error.message}. Falling back to broad/manual query path.`;
|
|
4861
|
+
const queryResult2 = await queryWithDegradedFallback({
|
|
4862
|
+
project: resolvedProject,
|
|
4863
|
+
query,
|
|
4864
|
+
top_k,
|
|
4865
|
+
include_memories: include_memories === true,
|
|
4866
|
+
include_graph,
|
|
4867
|
+
user_id: user_id || resolveMcpScope({ user_id }).userId,
|
|
4868
|
+
session_id: session_id || resolveMcpScope({ session_id }).sessionId
|
|
4869
|
+
});
|
|
4870
|
+
const response2 = queryResult2.response;
|
|
4871
|
+
if (response2.results.length === 0) {
|
|
4872
|
+
return { content: [{ type: "text", text: `No relevant context found.
|
|
4873
|
+
|
|
4874
|
+
[automatic_runtime]
|
|
4875
|
+
${automaticWarning}` }] };
|
|
4876
|
+
}
|
|
4877
|
+
const scope2 = resolveMcpScope({ user_id, session_id });
|
|
4878
|
+
const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope2.userId}, session=${scope2.sessionId}):
|
|
4879
|
+
|
|
4880
|
+
`;
|
|
4881
|
+
const suffix2 = queryResult2.degraded_mode ? `
|
|
4882
|
+
|
|
4883
|
+
[degraded_mode=true] ${queryResult2.degraded_reason}
|
|
4884
|
+
Recommendation: ${queryResult2.recommendation}` : "";
|
|
4885
|
+
return { content: [{ type: "text", text: `${header2}${response2.context}
|
|
4886
|
+
|
|
4887
|
+
[automatic_runtime]
|
|
4888
|
+
${automaticWarning}${suffix2}` }] };
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
2598
4891
|
const queryResult = await queryWithDegradedFallback({
|
|
2599
4892
|
project: resolvedProject,
|
|
2600
4893
|
query,
|
|
2601
4894
|
top_k,
|
|
2602
|
-
include_memories,
|
|
4895
|
+
include_memories: include_memories === true,
|
|
2603
4896
|
include_graph,
|
|
2604
|
-
user_id,
|
|
2605
|
-
session_id
|
|
4897
|
+
user_id: user_id || resolveMcpScope({ user_id }).userId,
|
|
4898
|
+
session_id: session_id || resolveMcpScope({ session_id }).sessionId
|
|
2606
4899
|
});
|
|
2607
4900
|
const response = queryResult.response;
|
|
2608
4901
|
if (response.results.length === 0) {
|
|
2609
4902
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
2610
4903
|
}
|
|
2611
|
-
const
|
|
4904
|
+
const scope = resolveMcpScope({ user_id, session_id });
|
|
4905
|
+
const header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
2612
4906
|
|
|
2613
4907
|
`;
|
|
2614
4908
|
const suffix = queryResult.degraded_mode ? `
|
|
@@ -2635,12 +4929,13 @@ server.tool(
|
|
|
2635
4929
|
},
|
|
2636
4930
|
async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
|
|
2637
4931
|
try {
|
|
4932
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
2638
4933
|
const result = await whisper.addMemory({
|
|
2639
|
-
project,
|
|
4934
|
+
project: scope.project,
|
|
2640
4935
|
content,
|
|
2641
4936
|
memory_type,
|
|
2642
|
-
user_id,
|
|
2643
|
-
session_id,
|
|
4937
|
+
user_id: scope.userId,
|
|
4938
|
+
session_id: scope.sessionId,
|
|
2644
4939
|
agent_id,
|
|
2645
4940
|
importance
|
|
2646
4941
|
});
|
|
@@ -2649,7 +4944,7 @@ server.tool(
|
|
|
2649
4944
|
const mode = result?.mode;
|
|
2650
4945
|
const semanticStatus = result?.semantic_status;
|
|
2651
4946
|
const typeLabel = memory_type || "factual";
|
|
2652
|
-
const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
|
|
4947
|
+
const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
|
|
2653
4948
|
return { content: [{ type: "text", text }] };
|
|
2654
4949
|
} catch (error) {
|
|
2655
4950
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -2669,11 +4964,21 @@ server.tool(
|
|
|
2669
4964
|
},
|
|
2670
4965
|
async ({ project, query, user_id, session_id, top_k, memory_types }) => {
|
|
2671
4966
|
try {
|
|
2672
|
-
const
|
|
2673
|
-
|
|
4967
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
4968
|
+
const results = runtimeClient && (!memory_types || memory_types.length <= 1) ? await runtimeClient.memory.search({
|
|
4969
|
+
project: scope.project,
|
|
2674
4970
|
query,
|
|
2675
|
-
user_id,
|
|
2676
|
-
session_id,
|
|
4971
|
+
user_id: scope.userId,
|
|
4972
|
+
session_id: scope.sessionId,
|
|
4973
|
+
top_k,
|
|
4974
|
+
include_pending: true,
|
|
4975
|
+
profile: "balanced",
|
|
4976
|
+
...memory_types?.length === 1 ? { memory_type: memory_types[0] } : {}
|
|
4977
|
+
}) : await whisper.searchMemoriesSOTA({
|
|
4978
|
+
project: scope.project,
|
|
4979
|
+
query,
|
|
4980
|
+
user_id: scope.userId,
|
|
4981
|
+
session_id: scope.sessionId,
|
|
2677
4982
|
top_k,
|
|
2678
4983
|
memory_types
|
|
2679
4984
|
});
|
|
@@ -2681,6 +4986,8 @@ server.tool(
|
|
|
2681
4986
|
return primaryToolSuccess({
|
|
2682
4987
|
tool: "memory.search",
|
|
2683
4988
|
query,
|
|
4989
|
+
user_id: scope.userId,
|
|
4990
|
+
session_id: scope.sessionId,
|
|
2684
4991
|
results: normalizedResults,
|
|
2685
4992
|
count: normalizedResults.length
|
|
2686
4993
|
});
|
|
@@ -2803,6 +5110,10 @@ server.tool(
|
|
|
2803
5110
|
max_duration_minutes: input.max_duration_minutes,
|
|
2804
5111
|
max_chunks: input.max_chunks
|
|
2805
5112
|
});
|
|
5113
|
+
noteAutomaticSourceActivity({
|
|
5114
|
+
project: resolvedProject,
|
|
5115
|
+
sourceIds: [result.source_id]
|
|
5116
|
+
});
|
|
2806
5117
|
return toTextResult(result);
|
|
2807
5118
|
} catch (error) {
|
|
2808
5119
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -2891,6 +5202,11 @@ server.tool(
|
|
|
2891
5202
|
strategy_override,
|
|
2892
5203
|
profile_config
|
|
2893
5204
|
});
|
|
5205
|
+
const sourceId = result?.source_id || result?.id;
|
|
5206
|
+
noteAutomaticSourceActivity({
|
|
5207
|
+
project: resolvedProject,
|
|
5208
|
+
sourceIds: sourceId ? [String(sourceId)] : []
|
|
5209
|
+
});
|
|
2894
5210
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2895
5211
|
}
|
|
2896
5212
|
if (!content?.trim()) {
|
|
@@ -2968,21 +5284,23 @@ server.tool(
|
|
|
2968
5284
|
},
|
|
2969
5285
|
async ({ project, session_id, user_id, messages }) => {
|
|
2970
5286
|
try {
|
|
5287
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
2971
5288
|
const normalizedMessages = messages.map((message) => ({
|
|
2972
5289
|
role: message.role,
|
|
2973
5290
|
content: message.content,
|
|
2974
5291
|
timestamp: message.timestamp
|
|
2975
5292
|
}));
|
|
2976
5293
|
const result = await whisper.ingestSession({
|
|
2977
|
-
project,
|
|
2978
|
-
session_id,
|
|
2979
|
-
user_id,
|
|
5294
|
+
project: scope.project,
|
|
5295
|
+
session_id: scope.sessionId,
|
|
5296
|
+
user_id: scope.userId,
|
|
2980
5297
|
messages: normalizedMessages
|
|
2981
5298
|
});
|
|
2982
5299
|
return {
|
|
2983
5300
|
content: [{
|
|
2984
5301
|
type: "text",
|
|
2985
5302
|
text: `Processed ${normalizedMessages.length} messages:
|
|
5303
|
+
- Scope ${scope.project || "auto-project"} / ${scope.userId} / ${scope.sessionId}
|
|
2986
5304
|
- Created ${result.memories_created} memories
|
|
2987
5305
|
- Detected ${result.relations_created} relations
|
|
2988
5306
|
- Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
|
|
@@ -4323,6 +6641,10 @@ server.tool(
|
|
|
4323
6641
|
max_chunks: input.max_chunks,
|
|
4324
6642
|
auto_index: true
|
|
4325
6643
|
});
|
|
6644
|
+
noteAutomaticSourceActivity({
|
|
6645
|
+
project: resolvedProject,
|
|
6646
|
+
sourceIds: [result.source_id]
|
|
6647
|
+
});
|
|
4326
6648
|
return toTextResult(result);
|
|
4327
6649
|
} catch (error) {
|
|
4328
6650
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -4503,5 +6825,6 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
|
4503
6825
|
export {
|
|
4504
6826
|
createMcpServer,
|
|
4505
6827
|
createWhisperMcpClient,
|
|
6828
|
+
createWhisperMcpRuntimeClient,
|
|
4506
6829
|
renderScopedMcpConfig
|
|
4507
6830
|
};
|