@usewhisper/mcp-server 2.3.0 → 2.5.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.
Files changed (2) hide show
  1. package/dist/server.js +2068 -146
  2. package/package.json +1 -1
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,1957 @@ 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 withTimeout(promise, timeoutMs) {
1369
+ return new Promise((resolve, reject) => {
1370
+ const timeout = setTimeout(() => {
1371
+ reject(new Error("timeout"));
1372
+ }, timeoutMs);
1373
+ promise.then(
1374
+ (value) => {
1375
+ clearTimeout(timeout);
1376
+ resolve(value);
1377
+ },
1378
+ (error) => {
1379
+ clearTimeout(timeout);
1380
+ reject(error);
1381
+ }
1382
+ );
1383
+ });
1384
+ }
1385
+ function extractTimestamp(metadata) {
1386
+ const candidates = [
1387
+ metadata?.updatedAt,
1388
+ metadata?.createdAt,
1389
+ metadata?.timestamp,
1390
+ metadata?.event_date,
1391
+ metadata?.eventDate
1392
+ ];
1393
+ for (const value of candidates) {
1394
+ if (typeof value === "string") {
1395
+ const parsed = Date.parse(value);
1396
+ if (!Number.isNaN(parsed)) return parsed;
1397
+ }
1398
+ }
1399
+ return 0;
1400
+ }
1401
+ function salienceBoost(metadata) {
1402
+ const value = metadata?.salience;
1403
+ if (value === "high") return 0.12;
1404
+ if (value === "medium") return 0.06;
1405
+ return 0;
1406
+ }
1407
+ var WhisperAgentRuntime = class {
1408
+ constructor(args) {
1409
+ this.args = args;
1410
+ this.bindingStore = createBindingStore(args.options.bindingStorePath);
1411
+ this.topK = args.options.topK ?? 6;
1412
+ this.maxTokens = args.options.maxTokens ?? 4e3;
1413
+ this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1414
+ this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1415
+ this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1416
+ this.baseContext = args.baseContext;
1417
+ this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1418
+ }
1419
+ bindingStore;
1420
+ topK;
1421
+ maxTokens;
1422
+ targetRetrievalMs;
1423
+ hardRetrievalTimeoutMs;
1424
+ recentWorkLimit;
1425
+ baseContext;
1426
+ clientName;
1427
+ bindings = null;
1428
+ touchedFiles = [];
1429
+ recentWork = [];
1430
+ bufferedLowSalience = [];
1431
+ lastPreparedTurn = null;
1432
+ mergedCount = 0;
1433
+ droppedCount = 0;
1434
+ lastScope = {};
1435
+ async getBindings() {
1436
+ if (!this.bindings) {
1437
+ this.bindings = await this.bindingStore.load();
1438
+ }
1439
+ return this.bindings;
1440
+ }
1441
+ pushTouchedFiles(paths) {
1442
+ if (!paths || paths.length === 0) return;
1443
+ for (const path of paths) {
1444
+ if (!path) continue;
1445
+ this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
1446
+ }
1447
+ }
1448
+ pushWorkEvent(event) {
1449
+ this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1450
+ }
1451
+ makeTaskFrameQuery(input) {
1452
+ const task = compactWhitespace(input.taskSummary || "");
1453
+ const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
1454
+ const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
1455
+ const parts = [
1456
+ task ? `task ${task}` : "",
1457
+ salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
1458
+ files.length > 0 ? `files ${files.join(" ")}` : "",
1459
+ input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
1460
+ ].filter(Boolean);
1461
+ if (parts.length === 0) return null;
1462
+ return parts.join(" | ");
1463
+ }
1464
+ async resolveScope(overrides) {
1465
+ const merged = {
1466
+ ...this.baseContext,
1467
+ ...overrides
1468
+ };
1469
+ const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
1470
+ const bindings = await this.getBindings();
1471
+ const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
1472
+ const configuredProject = merged.project;
1473
+ let projectRef = configuredProject;
1474
+ let projectSource = overrides?.project ? "explicit" : "generated";
1475
+ let warning;
1476
+ if (workspaceProject) {
1477
+ projectRef = workspaceProject;
1478
+ projectSource = "workspace";
1479
+ if (configuredProject && workspaceProject !== configuredProject) {
1480
+ warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
1481
+ }
1482
+ } else if (configuredProject) {
1483
+ projectRef = configuredProject;
1484
+ projectSource = overrides?.project ? "explicit" : "config";
1485
+ }
1486
+ const project = (await this.args.adapter.resolveProject(projectRef)).id;
1487
+ if (normalizedWorkspace) {
1488
+ bindings[normalizedWorkspace] = project;
1489
+ await this.bindingStore.save(bindings);
1490
+ }
1491
+ const scope = {
1492
+ ...merged,
1493
+ project,
1494
+ userId: merged.userId || `${this.clientName}-user`,
1495
+ sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
1496
+ };
1497
+ this.lastScope = {
1498
+ project: scope.project,
1499
+ userId: scope.userId,
1500
+ sessionId: scope.sessionId,
1501
+ source: projectSource,
1502
+ warning
1503
+ };
1504
+ return { scope, projectSource, warning };
1505
+ }
1506
+ async runBranch(name, task) {
1507
+ const startedAt = Date.now();
1508
+ try {
1509
+ const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
1510
+ return {
1511
+ name,
1512
+ status: "ok",
1513
+ durationMs: Date.now() - startedAt,
1514
+ value
1515
+ };
1516
+ } catch (error) {
1517
+ const durationMs = Date.now() - startedAt;
1518
+ const reason = error instanceof Error ? error.message : String(error);
1519
+ return {
1520
+ name,
1521
+ status: reason === "timeout" ? "timeout" : "error",
1522
+ durationMs,
1523
+ reason
1524
+ };
1525
+ }
1526
+ }
1527
+ contextItems(result, sourceQuery) {
1528
+ return (result.results || []).map((item) => ({
1529
+ id: item.id,
1530
+ content: item.content,
1531
+ type: "project",
1532
+ score: item.score ?? 0,
1533
+ sourceQuery,
1534
+ metadata: item.metadata || {}
1535
+ }));
1536
+ }
1537
+ memoryItems(result, sourceQuery) {
1538
+ return (result.results || []).map((item, index) => ({
1539
+ id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1540
+ content: item.chunk?.content || item.memory?.content || "",
1541
+ type: "memory",
1542
+ score: item.similarity ?? 0,
1543
+ sourceQuery,
1544
+ metadata: {
1545
+ ...item.chunk?.metadata || {},
1546
+ ...item.memory?.temporal || {},
1547
+ confidence: item.memory?.confidence
1548
+ }
1549
+ })).filter((item) => item.content);
1550
+ }
1551
+ rerank(items) {
1552
+ const deduped = /* @__PURE__ */ new Map();
1553
+ for (const item of items) {
1554
+ const key = `${item.id}:${item.content.toLowerCase()}`;
1555
+ const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1556
+ const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1557
+ const next = {
1558
+ ...item,
1559
+ score: item.score + queryBonus + salienceBoost(item.metadata) + recency
1560
+ };
1561
+ const existing = deduped.get(key);
1562
+ if (!existing || next.score > existing.score) {
1563
+ deduped.set(key, next);
1564
+ }
1565
+ }
1566
+ return [...deduped.values()].sort((left, right) => right.score - left.score);
1567
+ }
1568
+ buildContext(items) {
1569
+ const maxChars = this.maxTokens * 4;
1570
+ const lines = [];
1571
+ let used = 0;
1572
+ for (const item of items) {
1573
+ const label = item.type === "memory" ? "memory" : "context";
1574
+ const content = compactWhitespace(item.content);
1575
+ if (!content) continue;
1576
+ const line = `[${label}] ${content}`;
1577
+ if (used + line.length > maxChars) break;
1578
+ lines.push(line);
1579
+ used += line.length;
1580
+ }
1581
+ if (lines.length === 0) return "";
1582
+ return `Relevant context:
1583
+ ${lines.join("\n")}`;
1584
+ }
1585
+ async bootstrap(context = {}) {
1586
+ const { scope, warning } = await this.resolveScope(context);
1587
+ const warnings = warning ? [warning] : [];
1588
+ const startedAt = Date.now();
1589
+ const branches = await Promise.all([
1590
+ this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
1591
+ project: scope.project,
1592
+ session_id: scope.sessionId,
1593
+ include_pending: true,
1594
+ limit: 12
1595
+ })),
1596
+ this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
1597
+ project: scope.project,
1598
+ user_id: scope.userId,
1599
+ include_pending: true,
1600
+ memory_types: "preference,instruction,goal"
1601
+ }) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
1602
+ this.runBranch("project_rules", () => this.args.adapter.query({
1603
+ project: scope.project,
1604
+ query: "project rules instructions constraints conventions open threads",
1605
+ top_k: this.topK,
1606
+ include_memories: false,
1607
+ user_id: scope.userId,
1608
+ session_id: scope.sessionId,
1609
+ max_tokens: this.maxTokens,
1610
+ compress: true,
1611
+ compression_strategy: "adaptive"
1612
+ }))
1613
+ ]);
1614
+ const items = [];
1615
+ const branchStatus = {};
1616
+ for (const branch of branches) {
1617
+ branchStatus[branch.name] = branch.status;
1618
+ if (branch.status !== "ok") {
1619
+ if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1620
+ continue;
1621
+ }
1622
+ if (branch.name === "project_rules") {
1623
+ items.push(...this.contextItems(branch.value, "bootstrap"));
1624
+ continue;
1625
+ }
1626
+ const records = branch.value.memories || [];
1627
+ items.push(...records.map((memory, index) => ({
1628
+ id: String(memory.id || `${branch.name}_${index}`),
1629
+ content: String(memory.content || ""),
1630
+ type: "memory",
1631
+ score: 0.4,
1632
+ sourceQuery: "bootstrap",
1633
+ metadata: memory
1634
+ })).filter((item) => item.content));
1635
+ }
1636
+ const ranked = this.rerank(items).slice(0, this.topK * 2);
1637
+ const prepared = {
1638
+ scope,
1639
+ retrieval: {
1640
+ primaryQuery: "bootstrap",
1641
+ taskFrameQuery: null,
1642
+ warnings,
1643
+ degraded: warnings.length > 0,
1644
+ degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
1645
+ durationMs: Date.now() - startedAt,
1646
+ targetBudgetMs: this.targetRetrievalMs,
1647
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1648
+ branchStatus
1649
+ },
1650
+ context: this.buildContext(ranked),
1651
+ items: ranked
1652
+ };
1653
+ this.lastPreparedTurn = prepared.retrieval;
1654
+ return prepared;
1655
+ }
1656
+ async beforeTurn(input, context = {}) {
1657
+ this.pushTouchedFiles(input.touchedFiles);
1658
+ const { scope, warning } = await this.resolveScope(context);
1659
+ const primaryQuery = compactWhitespace(input.userMessage);
1660
+ const taskFrameQuery = this.makeTaskFrameQuery(input);
1661
+ const warnings = warning ? [warning] : [];
1662
+ const startedAt = Date.now();
1663
+ const branches = await Promise.all([
1664
+ this.runBranch("context_primary", () => this.args.adapter.query({
1665
+ project: scope.project,
1666
+ query: primaryQuery,
1667
+ top_k: this.topK,
1668
+ include_memories: false,
1669
+ user_id: scope.userId,
1670
+ session_id: scope.sessionId,
1671
+ max_tokens: this.maxTokens,
1672
+ compress: true,
1673
+ compression_strategy: "adaptive"
1674
+ })),
1675
+ this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
1676
+ project: scope.project,
1677
+ query: primaryQuery,
1678
+ user_id: scope.userId,
1679
+ session_id: scope.sessionId,
1680
+ top_k: this.topK,
1681
+ include_pending: true,
1682
+ profile: "balanced"
1683
+ })),
1684
+ taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
1685
+ project: scope.project,
1686
+ query: taskFrameQuery,
1687
+ top_k: this.topK,
1688
+ include_memories: false,
1689
+ user_id: scope.userId,
1690
+ session_id: scope.sessionId,
1691
+ max_tokens: this.maxTokens,
1692
+ compress: true,
1693
+ compression_strategy: "adaptive"
1694
+ })) : Promise.resolve({
1695
+ name: "context_task_frame",
1696
+ status: "skipped",
1697
+ durationMs: 0
1698
+ }),
1699
+ taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
1700
+ project: scope.project,
1701
+ query: taskFrameQuery,
1702
+ user_id: scope.userId,
1703
+ session_id: scope.sessionId,
1704
+ top_k: this.topK,
1705
+ include_pending: true,
1706
+ profile: "balanced"
1707
+ })) : Promise.resolve({
1708
+ name: "memory_task_frame",
1709
+ status: "skipped",
1710
+ durationMs: 0
1711
+ })
1712
+ ]);
1713
+ const branchStatus = {};
1714
+ const collected = [];
1715
+ let okCount = 0;
1716
+ for (const branch of branches) {
1717
+ branchStatus[branch.name] = branch.status;
1718
+ if (branch.status !== "ok") {
1719
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1720
+ continue;
1721
+ }
1722
+ okCount += 1;
1723
+ if (branch.name.startsWith("context")) {
1724
+ collected.push(...this.contextItems(
1725
+ branch.value,
1726
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
1727
+ ));
1728
+ } else {
1729
+ collected.push(...this.memoryItems(
1730
+ branch.value,
1731
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
1732
+ ));
1733
+ }
1734
+ }
1735
+ const ranked = this.rerank(collected).slice(0, this.topK * 2);
1736
+ const prepared = {
1737
+ scope,
1738
+ retrieval: {
1739
+ primaryQuery,
1740
+ taskFrameQuery,
1741
+ warnings,
1742
+ degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
1743
+ degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
1744
+ durationMs: Date.now() - startedAt,
1745
+ targetBudgetMs: this.targetRetrievalMs,
1746
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1747
+ branchStatus
1748
+ },
1749
+ context: this.buildContext(ranked),
1750
+ items: ranked
1751
+ };
1752
+ this.lastPreparedTurn = prepared.retrieval;
1753
+ return prepared;
1754
+ }
1755
+ async recordWork(event, context = {}) {
1756
+ const normalized = {
1757
+ ...event,
1758
+ salience: event.salience || defaultSalience(event.kind, event.success),
1759
+ timestamp: event.timestamp || nowIso()
1760
+ };
1761
+ this.pushTouchedFiles(normalized.filePaths);
1762
+ this.pushWorkEvent(normalized);
1763
+ if (normalized.salience === "low") {
1764
+ this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
1765
+ return { success: true, buffered: true };
1766
+ }
1767
+ const { scope } = await this.resolveScope(context);
1768
+ return this.args.adapter.addMemory({
1769
+ project: scope.project,
1770
+ user_id: scope.userId,
1771
+ session_id: scope.sessionId,
1772
+ content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
1773
+ memory_type: toMemoryType(normalized.kind),
1774
+ event_date: normalized.timestamp,
1775
+ write_mode: "async",
1776
+ metadata: {
1777
+ runtime_auto: true,
1778
+ client_name: this.clientName,
1779
+ work_event_kind: normalized.kind,
1780
+ salience: normalized.salience,
1781
+ file_paths: normalized.filePaths || [],
1782
+ tool_name: normalized.toolName,
1783
+ success: normalized.success
1784
+ }
1785
+ });
1786
+ }
1787
+ async afterTurn(input, context = {}) {
1788
+ this.pushTouchedFiles(input.touchedFiles);
1789
+ const { scope } = await this.resolveScope(context);
1790
+ const result = await this.args.adapter.ingestSession({
1791
+ project: scope.project,
1792
+ session_id: scope.sessionId,
1793
+ user_id: scope.userId,
1794
+ messages: [
1795
+ { role: "user", content: input.userMessage, timestamp: nowIso() },
1796
+ { role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
1797
+ ],
1798
+ write_mode: "async"
1799
+ });
1800
+ this.mergedCount += result.memories_invalidated || 0;
1801
+ return {
1802
+ success: Boolean(result.success),
1803
+ sessionIngested: true,
1804
+ memoriesCreated: result.memories_created || 0,
1805
+ relationsCreated: result.relations_created || 0,
1806
+ invalidatedCount: result.memories_invalidated || 0,
1807
+ mergedCount: result.memories_invalidated || 0,
1808
+ droppedCount: 0,
1809
+ warnings: result.errors || []
1810
+ };
1811
+ }
1812
+ async flush(reason = "manual", context = {}) {
1813
+ if (this.bufferedLowSalience.length > 0) {
1814
+ const { scope } = await this.resolveScope(context);
1815
+ const summary = summarizeLowSalience(this.bufferedLowSalience);
1816
+ await this.args.adapter.addMemory({
1817
+ project: scope.project,
1818
+ user_id: scope.userId,
1819
+ session_id: scope.sessionId,
1820
+ content: summary,
1821
+ memory_type: "event",
1822
+ write_mode: "async",
1823
+ metadata: {
1824
+ runtime_auto: true,
1825
+ client_name: this.clientName,
1826
+ flush_reason: reason,
1827
+ salience: "low",
1828
+ summarized_count: this.bufferedLowSalience.length
1829
+ }
1830
+ });
1831
+ this.bufferedLowSalience = [];
1832
+ }
1833
+ await this.args.adapter.flushQueue();
1834
+ return this.status();
1835
+ }
1836
+ status() {
1837
+ return {
1838
+ clientName: this.clientName,
1839
+ scope: this.lastScope,
1840
+ queue: this.args.adapter.queueStatus(),
1841
+ retrieval: this.lastPreparedTurn,
1842
+ counters: {
1843
+ mergedCount: this.mergedCount,
1844
+ droppedCount: this.droppedCount,
1845
+ bufferedLowSalience: this.bufferedLowSalience.length
1846
+ }
1847
+ };
1848
+ }
1849
+ };
1850
+
1851
+ // ../src/sdk/whisper.ts
1852
+ var PROJECT_CACHE_TTL_MS = 3e4;
1853
+ function isLikelyProjectId(projectRef) {
1854
+ 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);
1855
+ }
1856
+ var WhisperClient = class _WhisperClient {
1857
+ constructor(config) {
1858
+ this.config = config;
1859
+ this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
1860
+ this.runtimeClient = new RuntimeClient(
1861
+ {
1862
+ apiKey: config.apiKey,
1863
+ baseUrl: config.baseUrl,
1864
+ compatMode: config.compatMode || "fallback",
1865
+ timeouts: config.timeouts,
1866
+ retryPolicy: config.retryPolicy,
1867
+ fetchImpl: config.fetch
1868
+ },
1869
+ this.diagnosticsStore
1870
+ );
1871
+ this.searchCache = new SearchResponseCache(
1872
+ config.cache?.ttlMs ?? 7e3,
1873
+ config.cache?.capacity ?? 500
1874
+ );
1875
+ const queueStore = this.createQueueStore(config);
1876
+ this.writeQueue = new WriteQueue({
1877
+ store: queueStore,
1878
+ maxBatchSize: config.queue?.maxBatchSize ?? 50,
1879
+ flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
1880
+ maxAttempts: config.queue?.maxAttempts ?? 2,
1881
+ flushHandler: async (items) => {
1882
+ if (items.length === 0) return;
1883
+ const project = items[0].project;
1884
+ const memories = items.map((item) => ({
1885
+ ...item.payload,
1886
+ user_id: item.payload.user_id ?? item.userId,
1887
+ session_id: item.payload.session_id ?? item.sessionId,
1888
+ metadata: {
1889
+ ...item.payload.metadata || {},
1890
+ event_id: item.eventId,
1891
+ queued_at: item.createdAt
1892
+ }
1893
+ }));
1894
+ try {
1895
+ await this.runtimeClient.request({
1896
+ endpoint: "/v1/memory/bulk",
1897
+ method: "POST",
1898
+ operation: "bulk",
1899
+ body: {
1900
+ project,
1901
+ write_mode: "async",
1902
+ memories
1903
+ }
1904
+ });
1905
+ } catch (error) {
1906
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
1907
+ throw error;
1908
+ }
1909
+ await Promise.all(
1910
+ memories.map(async (memory) => {
1911
+ try {
1912
+ await this.runtimeClient.request({
1913
+ endpoint: "/v1/memory",
1914
+ method: "POST",
1915
+ operation: "writeAck",
1916
+ body: {
1917
+ project,
1918
+ ...memory,
1919
+ write_mode: "sync"
1920
+ }
1921
+ });
1922
+ } catch (fallbackError) {
1923
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
1924
+ throw fallbackError;
1925
+ }
1926
+ await this.runtimeClient.request({
1927
+ endpoint: "/v1/memories",
1928
+ method: "POST",
1929
+ operation: "writeAck",
1930
+ body: {
1931
+ project,
1932
+ ...memory,
1933
+ memory_type: memory.memory_type === "event" ? "episodic" : memory.memory_type
1934
+ }
1935
+ });
1936
+ }
1937
+ })
1938
+ );
1939
+ }
1940
+ }
1941
+ });
1942
+ if (config.queue?.enabled !== false) {
1943
+ void this.writeQueue.start();
1944
+ }
1945
+ this.memoryModule = new MemoryModule(
1946
+ this.runtimeClient,
1947
+ this.searchCache,
1948
+ this.writeQueue,
1949
+ {
1950
+ defaultProject: config.project,
1951
+ cacheEnabled: config.cache?.enabled !== false,
1952
+ queueEnabled: config.queue?.enabled !== false
1953
+ }
1954
+ );
1955
+ this.sessionModule = new SessionModule(this.memoryModule, config.project);
1956
+ this.profileModule = new ProfileModule(this.memoryModule);
1957
+ this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
1958
+ this.diagnostics = {
1959
+ getLast: (limit) => this.diagnosticsStore.getLast(limit),
1960
+ subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
1961
+ snapshot: () => this.diagnosticsStore.snapshot()
1962
+ };
1963
+ this.queue = {
1964
+ flush: () => this.writeQueue.flush(),
1965
+ status: () => this.writeQueue.status()
1966
+ };
1967
+ this.memory = {
1968
+ add: (params) => this.memoryModule.add(params),
1969
+ addBulk: (params) => this.memoryModule.addBulk(params),
1970
+ search: (params) => this.memoryModule.search(params),
1971
+ get: (memoryId) => this.memoryModule.get(memoryId),
1972
+ getUserProfile: (params) => this.memoryModule.getUserProfile(params),
1973
+ getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
1974
+ update: (memoryId, params) => this.memoryModule.update(memoryId, params),
1975
+ delete: (memoryId) => this.memoryModule.delete(memoryId),
1976
+ flag: (params) => this.memoryModule.flag(params)
1977
+ };
1978
+ this.session = {
1979
+ start: (params) => this.sessionModule.start(params),
1980
+ event: (params) => this.sessionModule.event(params),
1981
+ suspend: (params) => this.sessionModule.suspend(params),
1982
+ resume: (params) => this.sessionModule.resume(params),
1983
+ end: (params) => this.sessionModule.end(params)
1984
+ };
1985
+ this.profile = {
1986
+ getUserProfile: (params) => this.profileModule.getUserProfile(params),
1987
+ getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
1988
+ };
1989
+ this.analytics = {
1990
+ diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
1991
+ queueStatus: () => this.analyticsModule.queueStatus()
1992
+ };
1993
+ }
1994
+ diagnostics;
1995
+ queue;
1996
+ memory;
1997
+ session;
1998
+ profile;
1999
+ analytics;
2000
+ runtimeClient;
2001
+ diagnosticsStore;
2002
+ searchCache;
2003
+ writeQueue;
2004
+ memoryModule;
2005
+ sessionModule;
2006
+ profileModule;
2007
+ analyticsModule;
2008
+ projectRefToId = /* @__PURE__ */ new Map();
2009
+ projectCache = [];
2010
+ projectCacheExpiresAt = 0;
2011
+ static fromEnv(overrides = {}) {
2012
+ const env = typeof process !== "undefined" ? process.env : {};
2013
+ const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
2014
+ if (!apiKey) {
2015
+ throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
2016
+ }
2017
+ return new _WhisperClient({
2018
+ apiKey,
2019
+ baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
2020
+ project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
2021
+ ...overrides
2022
+ });
2023
+ }
2024
+ createQueueStore(config) {
2025
+ const persistence = config.queue?.persistence || this.defaultQueuePersistence();
2026
+ if (persistence === "storage") {
2027
+ return createStorageQueueStore();
2028
+ }
2029
+ if (persistence === "file") {
2030
+ const filePath = config.queue?.filePath || this.defaultQueueFilePath();
2031
+ if (filePath) {
2032
+ return createFileQueueStore(filePath);
2033
+ }
2034
+ }
2035
+ return new InMemoryQueueStore();
2036
+ }
2037
+ defaultQueuePersistence() {
2038
+ const maybeWindow = globalThis.window;
2039
+ if (maybeWindow && typeof maybeWindow === "object") {
2040
+ const maybeStorage = globalThis.localStorage;
2041
+ return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
2042
+ }
2043
+ return "file";
2044
+ }
2045
+ defaultQueueFilePath() {
2046
+ if (typeof process === "undefined") return void 0;
2047
+ const path = process.env.WHISPER_QUEUE_FILE_PATH;
2048
+ if (path) return path;
2049
+ const home = process.env.USERPROFILE || process.env.HOME;
2050
+ if (!home) return void 0;
2051
+ const normalizedHome = home.replace(/[\\\/]+$/, "");
2052
+ return `${normalizedHome}/.whisper/sdk/queue.json`;
2053
+ }
2054
+ getRequiredProject(project) {
2055
+ const resolved = project || this.config.project;
2056
+ if (!resolved) {
2057
+ throw new RuntimeClientError({
2058
+ code: "MISSING_PROJECT",
2059
+ message: "Project is required",
2060
+ retryable: false
2061
+ });
2062
+ }
2063
+ return resolved;
2064
+ }
2065
+ async refreshProjectCache(force = false) {
2066
+ if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
2067
+ return this.projectCache;
2068
+ }
2069
+ const response = await this.runtimeClient.request({
2070
+ endpoint: "/v1/projects",
2071
+ method: "GET",
2072
+ operation: "get",
2073
+ idempotent: true
272
2074
  });
273
- if (dedupeKey) {
274
- this.inFlight.set(dedupeKey, runner);
2075
+ this.projectRefToId.clear();
2076
+ this.projectCache = response.data?.projects || [];
2077
+ for (const project of this.projectCache) {
2078
+ this.projectRefToId.set(project.id, project.id);
2079
+ this.projectRefToId.set(project.slug, project.id);
2080
+ this.projectRefToId.set(project.name, project.id);
275
2081
  }
276
- return runner;
2082
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2083
+ return this.projectCache;
277
2084
  }
278
- async performRequest(options) {
279
- const method = options.method || "GET";
280
- const normalizedEndpoint = normalizeEndpoint(options.endpoint);
281
- const operation = options.operation;
282
- const maxAttempts = this.maxAttemptsFor(operation);
283
- const timeoutMs = this.timeoutFor(operation);
284
- const traceId = options.traceId || randomId("trace");
285
- let lastError = null;
286
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
287
- const spanId = randomId("span");
288
- const startedAt = Date.now();
289
- const controller = new AbortController();
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
- }
2085
+ async fetchResolvedProject(projectRef) {
2086
+ try {
2087
+ const response = await this.runtimeClient.request({
2088
+ endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
2089
+ method: "GET",
2090
+ operation: "get",
2091
+ idempotent: true
2092
+ });
2093
+ return response.data?.resolved || null;
2094
+ } catch (error) {
2095
+ if (error instanceof RuntimeClientError && error.status === 404) {
2096
+ return null;
380
2097
  }
381
- await new Promise((resolve) => setTimeout(resolve, this.backoff(attempt)));
2098
+ throw error;
382
2099
  }
383
- throw lastError || new RuntimeClientError({
384
- message: "Request failed",
385
- retryable: false,
386
- code: "REQUEST_FAILED"
2100
+ }
2101
+ async resolveProject(projectRef) {
2102
+ const resolvedRef = this.getRequiredProject(projectRef);
2103
+ const cachedProjects = await this.refreshProjectCache(false);
2104
+ const cachedProject = cachedProjects.find(
2105
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2106
+ );
2107
+ if (cachedProject) {
2108
+ return cachedProject;
2109
+ }
2110
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2111
+ if (resolvedProject) {
2112
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2113
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2114
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2115
+ this.projectCache = [
2116
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2117
+ resolvedProject
2118
+ ];
2119
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2120
+ return resolvedProject;
2121
+ }
2122
+ if (isLikelyProjectId(resolvedRef)) {
2123
+ return {
2124
+ id: resolvedRef,
2125
+ orgId: "",
2126
+ name: resolvedRef,
2127
+ slug: resolvedRef,
2128
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2129
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2130
+ };
2131
+ }
2132
+ throw new RuntimeClientError({
2133
+ code: "PROJECT_NOT_FOUND",
2134
+ message: `Project '${resolvedRef}' not found`,
2135
+ retryable: false
2136
+ });
2137
+ }
2138
+ async query(params) {
2139
+ const project = (await this.resolveProject(params.project)).id;
2140
+ const response = await this.runtimeClient.request({
2141
+ endpoint: "/v1/context/query",
2142
+ method: "POST",
2143
+ operation: "search",
2144
+ body: {
2145
+ ...params,
2146
+ project
2147
+ },
2148
+ idempotent: true
2149
+ });
2150
+ return response.data;
2151
+ }
2152
+ async ingestSession(params) {
2153
+ const project = (await this.resolveProject(params.project)).id;
2154
+ const response = await this.runtimeClient.request({
2155
+ endpoint: "/v1/memory/ingest/session",
2156
+ method: "POST",
2157
+ operation: "session",
2158
+ body: {
2159
+ ...params,
2160
+ project
2161
+ }
2162
+ });
2163
+ return response.data;
2164
+ }
2165
+ createAgentRuntime(options = {}) {
2166
+ const baseContext = {
2167
+ workspacePath: options.workspacePath,
2168
+ project: options.project || this.config.project,
2169
+ userId: options.userId,
2170
+ sessionId: options.sessionId,
2171
+ traceId: options.traceId,
2172
+ clientName: options.clientName
2173
+ };
2174
+ return new WhisperAgentRuntime({
2175
+ baseContext,
2176
+ options,
2177
+ adapter: {
2178
+ resolveProject: (project) => this.resolveProject(project),
2179
+ query: (params) => this.query(params),
2180
+ ingestSession: (params) => this.ingestSession(params),
2181
+ getSessionMemories: (params) => this.memory.getSessionMemories(params),
2182
+ getUserProfile: (params) => this.memory.getUserProfile(params),
2183
+ searchMemories: (params) => this.memory.search(params),
2184
+ addMemory: (params) => this.memory.add(params),
2185
+ queueStatus: () => this.queue.status(),
2186
+ flushQueue: () => this.queue.flush()
2187
+ }
387
2188
  });
388
2189
  }
2190
+ withRunContext(context) {
2191
+ const base = this;
2192
+ return {
2193
+ memory: {
2194
+ add: (params) => base.memory.add({
2195
+ ...params,
2196
+ project: params.project || context.project || base.config.project,
2197
+ user_id: params.user_id || context.userId,
2198
+ session_id: params.session_id || context.sessionId
2199
+ }),
2200
+ search: (params) => base.memory.search({
2201
+ ...params,
2202
+ project: params.project || context.project || base.config.project,
2203
+ user_id: params.user_id || context.userId,
2204
+ session_id: params.session_id || context.sessionId
2205
+ })
2206
+ },
2207
+ session: {
2208
+ event: (params) => base.session.event({
2209
+ ...params,
2210
+ sessionId: params.sessionId || context.sessionId || ""
2211
+ })
2212
+ },
2213
+ queue: base.queue,
2214
+ diagnostics: base.diagnostics
2215
+ };
2216
+ }
2217
+ async shutdown() {
2218
+ await this.writeQueue.stop();
2219
+ }
389
2220
  };
390
2221
 
391
2222
  // ../src/sdk/index.ts
@@ -407,7 +2238,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
407
2238
  var DEFAULT_BASE_DELAY_MS = 250;
408
2239
  var DEFAULT_MAX_DELAY_MS = 2e3;
409
2240
  var DEFAULT_TIMEOUT_MS = 15e3;
410
- var PROJECT_CACHE_TTL_MS = 3e4;
2241
+ var PROJECT_CACHE_TTL_MS2 = 3e4;
411
2242
  var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
412
2243
  function warnDeprecatedOnce(key, message) {
413
2244
  if (DEPRECATION_WARNINGS.has(key)) return;
@@ -416,7 +2247,7 @@ function warnDeprecatedOnce(key, message) {
416
2247
  console.warn(message);
417
2248
  }
418
2249
  }
419
- function isLikelyProjectId(projectRef) {
2250
+ function isLikelyProjectId2(projectRef) {
420
2251
  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
2252
  }
422
2253
  function normalizeBaseUrl2(url) {
@@ -538,7 +2369,7 @@ var WhisperContext = class _WhisperContext {
538
2369
  }
539
2370
  }
540
2371
  }
541
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2372
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
542
2373
  return this.projectCache;
543
2374
  }
544
2375
  async fetchResolvedProject(projectRef) {
@@ -571,10 +2402,10 @@ var WhisperContext = class _WhisperContext {
571
2402
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
572
2403
  resolvedProject
573
2404
  ];
574
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2405
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
575
2406
  return resolvedProject;
576
2407
  }
577
- if (isLikelyProjectId(resolvedRef)) {
2408
+ if (isLikelyProjectId2(resolvedRef)) {
578
2409
  return {
579
2410
  id: resolvedRef,
580
2411
  orgId: "",
@@ -606,7 +2437,7 @@ var WhisperContext = class _WhisperContext {
606
2437
  message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
607
2438
  });
608
2439
  }
609
- if (isLikelyProjectId(projectRef)) {
2440
+ if (isLikelyProjectId2(projectRef)) {
610
2441
  return projectRef;
611
2442
  }
612
2443
  const resolvedProject = await this.fetchResolvedProject(projectRef);
@@ -618,7 +2449,7 @@ var WhisperContext = class _WhisperContext {
618
2449
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
619
2450
  resolvedProject
620
2451
  ];
621
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2452
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
622
2453
  return resolvedProject.id;
623
2454
  }
624
2455
  throw new WhisperError({
@@ -635,7 +2466,7 @@ var WhisperContext = class _WhisperContext {
635
2466
  candidates.add(match.id);
636
2467
  candidates.add(match.slug);
637
2468
  candidates.add(match.name);
638
- } else if (isLikelyProjectId(projectRef)) {
2469
+ } else if (isLikelyProjectId2(projectRef)) {
639
2470
  const byId = projects.find((p) => p.id === projectRef);
640
2471
  if (byId) {
641
2472
  candidates.add(byId.slug);
@@ -796,7 +2627,7 @@ var WhisperContext = class _WhisperContext {
796
2627
  ...this.projectCache.filter((p) => p.id !== project.id),
797
2628
  project
798
2629
  ];
799
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2630
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
800
2631
  return project;
801
2632
  }
802
2633
  async listProjects() {
@@ -807,7 +2638,7 @@ var WhisperContext = class _WhisperContext {
807
2638
  this.projectRefToId.set(p.slug, p.id);
808
2639
  this.projectRefToId.set(p.name, p.id);
809
2640
  }
810
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2641
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
811
2642
  return projects;
812
2643
  }
813
2644
  async getProject(id) {
@@ -929,7 +2760,7 @@ var WhisperContext = class _WhisperContext {
929
2760
  async addMemory(params) {
930
2761
  const projectRef = this.getRequiredProject(params.project);
931
2762
  return this.withProjectRefFallback(projectRef, async (project) => {
932
- const toSotaType = (memoryType) => {
2763
+ const toSotaType2 = (memoryType) => {
933
2764
  switch (memoryType) {
934
2765
  case "episodic":
935
2766
  return "event";
@@ -941,7 +2772,7 @@ var WhisperContext = class _WhisperContext {
941
2772
  return memoryType;
942
2773
  }
943
2774
  };
944
- const toLegacyType = (memoryType) => {
2775
+ const toLegacyType2 = (memoryType) => {
945
2776
  switch (memoryType) {
946
2777
  case "event":
947
2778
  return "episodic";
@@ -962,7 +2793,7 @@ var WhisperContext = class _WhisperContext {
962
2793
  body: JSON.stringify({
963
2794
  project,
964
2795
  content: params.content,
965
- memory_type: toSotaType(params.memory_type),
2796
+ memory_type: toSotaType2(params.memory_type),
966
2797
  user_id: params.user_id,
967
2798
  session_id: params.session_id,
968
2799
  agent_id: params.agent_id,
@@ -1016,7 +2847,7 @@ var WhisperContext = class _WhisperContext {
1016
2847
  body: JSON.stringify({
1017
2848
  project,
1018
2849
  content: params.content,
1019
- memory_type: toLegacyType(params.memory_type),
2850
+ memory_type: toLegacyType2(params.memory_type),
1020
2851
  user_id: params.user_id,
1021
2852
  session_id: params.session_id,
1022
2853
  agent_id: params.agent_id,
@@ -1577,7 +3408,19 @@ function createWhisperMcpClient(options) {
1577
3408
  ...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
1578
3409
  });
1579
3410
  }
3411
+ function createWhisperMcpRuntimeClient(options) {
3412
+ const apiKey = options?.apiKey ?? API_KEY;
3413
+ if (!apiKey || IS_MANAGEMENT_ONLY) {
3414
+ return null;
3415
+ }
3416
+ return new WhisperClient({
3417
+ apiKey,
3418
+ project: options?.project ?? DEFAULT_PROJECT,
3419
+ ...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
3420
+ });
3421
+ }
1580
3422
  var whisper = createWhisperMcpClient();
3423
+ var runtimeClient = createWhisperMcpRuntimeClient();
1581
3424
  var server = new McpServer({
1582
3425
  name: "whisper-context",
1583
3426
  version: "0.2.8"
@@ -1739,6 +3582,7 @@ function computeChecksum(value) {
1739
3582
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
1740
3583
  }
1741
3584
  var cachedProjectRef = DEFAULT_PROJECT || void 0;
3585
+ var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
1742
3586
  async function resolveProjectRef(explicit) {
1743
3587
  if (explicit?.trim()) {
1744
3588
  const requestedRef = explicit.trim();
@@ -1762,6 +3606,37 @@ async function resolveProjectRef(explicit) {
1762
3606
  return void 0;
1763
3607
  }
1764
3608
  }
3609
+ function defaultMcpUserId() {
3610
+ const explicit = process.env.WHISPER_USER_ID?.trim();
3611
+ if (explicit) return explicit;
3612
+ const seed = `${process.cwd()}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12) || "anon"}`;
3613
+ return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
3614
+ }
3615
+ function resolveMcpScope(params) {
3616
+ return {
3617
+ project: params?.project,
3618
+ userId: params?.user_id?.trim() || defaultMcpUserId(),
3619
+ sessionId: params?.session_id?.trim() || cachedMcpSessionId,
3620
+ workspacePath: process.env.WHISPER_WORKSPACE_PATH || process.cwd()
3621
+ };
3622
+ }
3623
+ async function prepareAutomaticQuery(params) {
3624
+ if (!runtimeClient) {
3625
+ throw new Error("Whisper runtime client unavailable.");
3626
+ }
3627
+ const scope = resolveMcpScope(params);
3628
+ const runtime = runtimeClient.createAgentRuntime({
3629
+ project: params.project,
3630
+ userId: scope.userId,
3631
+ sessionId: scope.sessionId,
3632
+ workspacePath: scope.workspacePath,
3633
+ topK: params.top_k,
3634
+ clientName: "whisper-mcp"
3635
+ });
3636
+ return runtime.beforeTurn({
3637
+ userMessage: params.query
3638
+ });
3639
+ }
1765
3640
  function buildAbstain(args) {
1766
3641
  return {
1767
3642
  status: "abstained",
@@ -1857,6 +3732,8 @@ async function queryWithDegradedFallback(params) {
1857
3732
  top_k: params.top_k,
1858
3733
  include_memories: params.include_memories,
1859
3734
  include_graph: params.include_graph,
3735
+ user_id: params.user_id,
3736
+ session_id: params.session_id,
1860
3737
  hybrid: true,
1861
3738
  rerank: true
1862
3739
  });
@@ -1869,6 +3746,8 @@ async function queryWithDegradedFallback(params) {
1869
3746
  top_k: params.top_k,
1870
3747
  include_memories: params.include_memories,
1871
3748
  include_graph: false,
3749
+ user_id: params.user_id,
3750
+ session_id: params.session_id,
1872
3751
  hybrid: false,
1873
3752
  rerank: false,
1874
3753
  vector_weight: 0,
@@ -2583,7 +4462,7 @@ server.tool(
2583
4462
  query: z.string().describe("What are you looking for?"),
2584
4463
  top_k: z.number().optional().default(10).describe("Number of results"),
2585
4464
  chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
2586
- include_memories: z.boolean().optional().default(false).describe("Include relevant memories"),
4465
+ include_memories: z.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
2587
4466
  include_graph: z.boolean().optional().default(false).describe("Include knowledge graph traversal"),
2588
4467
  user_id: z.string().optional().describe("User ID for memory scoping"),
2589
4468
  session_id: z.string().optional().describe("Session ID for memory scoping"),
@@ -2595,20 +4474,47 @@ server.tool(
2595
4474
  if (!resolvedProject) {
2596
4475
  return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
2597
4476
  }
4477
+ const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
4478
+ if (automaticMode) {
4479
+ const prepared = await prepareAutomaticQuery({
4480
+ project: resolvedProject,
4481
+ query,
4482
+ top_k,
4483
+ user_id,
4484
+ session_id
4485
+ });
4486
+ if (!prepared.items.length) {
4487
+ return { content: [{ type: "text", text: "No relevant context found." }] };
4488
+ }
4489
+ const warnings = prepared.retrieval.warnings.length ? `
4490
+
4491
+ [automatic_runtime]
4492
+ ${prepared.retrieval.warnings.join("\n")}` : "";
4493
+ const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
4494
+ return {
4495
+ content: [{
4496
+ type: "text",
4497
+ text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}):
4498
+
4499
+ ${prepared.context}${warnings}`
4500
+ }]
4501
+ };
4502
+ }
2598
4503
  const queryResult = await queryWithDegradedFallback({
2599
4504
  project: resolvedProject,
2600
4505
  query,
2601
4506
  top_k,
2602
- include_memories,
4507
+ include_memories: include_memories === true,
2603
4508
  include_graph,
2604
- user_id,
2605
- session_id
4509
+ user_id: user_id || resolveMcpScope({ user_id }).userId,
4510
+ session_id: session_id || resolveMcpScope({ session_id }).sessionId
2606
4511
  });
2607
4512
  const response = queryResult.response;
2608
4513
  if (response.results.length === 0) {
2609
4514
  return { content: [{ type: "text", text: "No relevant context found." }] };
2610
4515
  }
2611
- const header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}):
4516
+ const scope = resolveMcpScope({ user_id, session_id });
4517
+ 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
4518
 
2613
4519
  `;
2614
4520
  const suffix = queryResult.degraded_mode ? `
@@ -2635,12 +4541,13 @@ server.tool(
2635
4541
  },
2636
4542
  async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
2637
4543
  try {
4544
+ const scope = resolveMcpScope({ project, user_id, session_id });
2638
4545
  const result = await whisper.addMemory({
2639
- project,
4546
+ project: scope.project,
2640
4547
  content,
2641
4548
  memory_type,
2642
- user_id,
2643
- session_id,
4549
+ user_id: scope.userId,
4550
+ session_id: scope.sessionId,
2644
4551
  agent_id,
2645
4552
  importance
2646
4553
  });
@@ -2649,7 +4556,7 @@ server.tool(
2649
4556
  const mode = result?.mode;
2650
4557
  const semanticStatus = result?.semantic_status;
2651
4558
  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}` : ""}).`;
4559
+ 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
4560
  return { content: [{ type: "text", text }] };
2654
4561
  } catch (error) {
2655
4562
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
@@ -2669,11 +4576,21 @@ server.tool(
2669
4576
  },
2670
4577
  async ({ project, query, user_id, session_id, top_k, memory_types }) => {
2671
4578
  try {
2672
- const results = await whisper.searchMemoriesSOTA({
2673
- project,
4579
+ const scope = resolveMcpScope({ project, user_id, session_id });
4580
+ const results = runtimeClient && (!memory_types || memory_types.length <= 1) ? await runtimeClient.memory.search({
4581
+ project: scope.project,
2674
4582
  query,
2675
- user_id,
2676
- session_id,
4583
+ user_id: scope.userId,
4584
+ session_id: scope.sessionId,
4585
+ top_k,
4586
+ include_pending: true,
4587
+ profile: "balanced",
4588
+ ...memory_types?.length === 1 ? { memory_type: memory_types[0] } : {}
4589
+ }) : await whisper.searchMemoriesSOTA({
4590
+ project: scope.project,
4591
+ query,
4592
+ user_id: scope.userId,
4593
+ session_id: scope.sessionId,
2677
4594
  top_k,
2678
4595
  memory_types
2679
4596
  });
@@ -2681,6 +4598,8 @@ server.tool(
2681
4598
  return primaryToolSuccess({
2682
4599
  tool: "memory.search",
2683
4600
  query,
4601
+ user_id: scope.userId,
4602
+ session_id: scope.sessionId,
2684
4603
  results: normalizedResults,
2685
4604
  count: normalizedResults.length
2686
4605
  });
@@ -2968,21 +4887,23 @@ server.tool(
2968
4887
  },
2969
4888
  async ({ project, session_id, user_id, messages }) => {
2970
4889
  try {
4890
+ const scope = resolveMcpScope({ project, user_id, session_id });
2971
4891
  const normalizedMessages = messages.map((message) => ({
2972
4892
  role: message.role,
2973
4893
  content: message.content,
2974
4894
  timestamp: message.timestamp
2975
4895
  }));
2976
4896
  const result = await whisper.ingestSession({
2977
- project,
2978
- session_id,
2979
- user_id,
4897
+ project: scope.project,
4898
+ session_id: scope.sessionId,
4899
+ user_id: scope.userId,
2980
4900
  messages: normalizedMessages
2981
4901
  });
2982
4902
  return {
2983
4903
  content: [{
2984
4904
  type: "text",
2985
4905
  text: `Processed ${normalizedMessages.length} messages:
4906
+ - Scope ${scope.project || "auto-project"} / ${scope.userId} / ${scope.sessionId}
2986
4907
  - Created ${result.memories_created} memories
2987
4908
  - Detected ${result.relations_created} relations
2988
4909
  - Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
@@ -4503,5 +6424,6 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
4503
6424
  export {
4504
6425
  createMcpServer,
4505
6426
  createWhisperMcpClient,
6427
+ createWhisperMcpRuntimeClient,
4506
6428
  renderScopedMcpConfig
4507
6429
  };