@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.
Files changed (3) hide show
  1. package/README.md +17 -0
  2. package/dist/server.js +2472 -149
  3. 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
- if (dedupeKey) {
274
- this.inFlight.set(dedupeKey, runner);
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
- return runner;
2394
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2395
+ return this.projectCache;
277
2396
  }
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
- }
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
- await new Promise((resolve) => setTimeout(resolve, this.backoff(attempt)));
2410
+ throw error;
382
2411
  }
383
- throw lastError || new RuntimeClientError({
384
- message: "Request failed",
385
- retryable: false,
386
- code: "REQUEST_FAILED"
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 PROJECT_CACHE_TTL_MS = 3e4;
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 isLikelyProjectId(projectRef) {
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() + PROJECT_CACHE_TTL_MS;
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() + PROJECT_CACHE_TTL_MS;
2717
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
575
2718
  return resolvedProject;
576
2719
  }
577
- if (isLikelyProjectId(resolvedRef)) {
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 (isLikelyProjectId(projectRef)) {
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() + PROJECT_CACHE_TTL_MS;
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 (isLikelyProjectId(projectRef)) {
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() + PROJECT_CACHE_TTL_MS;
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() + PROJECT_CACHE_TTL_MS;
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 toSotaType = (memoryType) => {
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 toLegacyType = (memoryType) => {
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: toSotaType(params.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: toLegacyType(params.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.2.8"
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 clamp01(value) {
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: clamp01(Number(source.score ?? metadata.score ?? 0)),
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().default(false).describe("Include relevant memories"),
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 header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}):
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 results = await whisper.searchMemoriesSOTA({
2673
- project,
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
  };