bikky 0.4.1 → 0.4.3

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 (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CODE_OF_CONDUCT.md +1 -1
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +23 -17
  5. package/SUPPORT.md +3 -2
  6. package/dist/config.d.ts +11 -1
  7. package/dist/config.js +88 -20
  8. package/dist/daemon/capture-policy.d.ts +0 -1
  9. package/dist/daemon/capture-policy.js +0 -1
  10. package/dist/daemon/consolidation.d.ts +2 -1
  11. package/dist/daemon/consolidation.js +28 -11
  12. package/dist/daemon/entity-typing.js +10 -0
  13. package/dist/daemon/episode-summary.d.ts +4 -0
  14. package/dist/daemon/episode-summary.js +39 -8
  15. package/dist/daemon/extraction.d.ts +1 -1
  16. package/dist/daemon/extraction.js +52 -17
  17. package/dist/daemon/qdrant.d.ts +32 -10
  18. package/dist/daemon/qdrant.js +177 -60
  19. package/dist/daemon/relations.d.ts +3 -3
  20. package/dist/daemon/relations.js +27 -15
  21. package/dist/daemon/session-index.d.ts +5 -0
  22. package/dist/daemon/session-index.js +36 -9
  23. package/dist/daemon/session-summary.d.ts +3 -0
  24. package/dist/daemon/session-summary.js +48 -15
  25. package/dist/daemon/staleness.js +2 -2
  26. package/dist/daemon/transcript-sources.js +3 -2
  27. package/dist/daemon/watcher.js +2 -0
  28. package/dist/daemon/workstream-summary.d.ts +4 -0
  29. package/dist/daemon/workstream-summary.js +58 -16
  30. package/dist/install.d.ts +11 -0
  31. package/dist/install.js +38 -0
  32. package/dist/llm/embedding/index.js +2 -1
  33. package/dist/llm/embedding/providers/openai.js +8 -2
  34. package/dist/llm/embedding/providers/portkey.js +9 -2
  35. package/dist/llm/inference/index.js +2 -1
  36. package/dist/llm/util.d.ts +12 -0
  37. package/dist/llm/util.js +18 -0
  38. package/dist/mcp/helpers.d.ts +5 -0
  39. package/dist/mcp/helpers.js +27 -3
  40. package/dist/mcp/taxonomy.js +12 -1
  41. package/dist/mcp/tools.js +161 -57
  42. package/dist/mcp/types.d.ts +12 -0
  43. package/dist/package-verifier.d.ts +19 -0
  44. package/dist/package-verifier.js +83 -0
  45. package/dist/provenance/origin.d.ts +57 -0
  46. package/dist/provenance/origin.js +254 -0
  47. package/docs/config/fully-hosted.md +33 -13
  48. package/docs/config/hosted-models.md +33 -13
  49. package/docs/configuration.md +23 -5
  50. package/package.json +6 -2
@@ -13,6 +13,7 @@ import { CAPTURE_POLICY_VERSION, CAPTURE_TRIGGERS, DEFAULT_CAPTURE_CONTEXT, PROM
13
13
  import * as qdrant from "./qdrant.js";
14
14
  import { combineRedactions, redactStorageText, } from "../privacy/redaction.js";
15
15
  import { resolveWorkstreamKey, } from "./workstream-resolver.js";
16
+ import { buildOperationOrigin } from "../provenance/origin.js";
16
17
  export { buildEpisodeSummaryMessages } from "../prompts/index.js";
17
18
  const DEFAULT_EPISODE_IMPORTANCE = 0.75;
18
19
  const contentHash = (text) => createHash("sha256").update(`episode:${text}`).digest("hex");
@@ -120,6 +121,17 @@ export const buildEpisodeSummaryPayload = (input) => {
120
121
  ]);
121
122
  const existingPayload = input.existing?.payload ?? {};
122
123
  const workstreamKey = input.draft.workstream_key ?? input.segment.workstream_key ?? null;
124
+ const operationOrigin = input.origin ?? buildOperationOrigin({
125
+ interface: "daemon",
126
+ action: input.existing ? "update" : "create",
127
+ subsystem: "episode_summary",
128
+ config: input.config,
129
+ metadata: {
130
+ session_id: input.sessionId,
131
+ episode_id: input.segment.episode_id,
132
+ event_count: input.segment.event_count,
133
+ },
134
+ });
123
135
  const payload = {
124
136
  ...existingPayload,
125
137
  content: redactedContent.text,
@@ -129,9 +141,9 @@ export const buildEpisodeSummaryPayload = (input) => {
129
141
  memory_subtype: "episode",
130
142
  layer: "episode",
131
143
  ...(input.scope.workspaceId ? { workspace_id: input.scope.workspaceId } : {}),
132
- ...(input.scope.actorId ? { actor_id: input.scope.actorId } : {}),
144
+ origin: existingPayload.origin ?? operationOrigin,
145
+ ...(input.existing ? { last_operation_origin: operationOrigin } : {}),
133
146
  entities: redactedEntities.map((entity) => entity.text.toLowerCase()),
134
- source: "system",
135
147
  confidence: 1.0,
136
148
  importance: input.draft.importance,
137
149
  content_hash: contentHash(redactedContent.text),
@@ -184,19 +196,36 @@ const summarizeEpisodeTranscript = async (input) => {
184
196
  throw new Error("Episode summary LLM returned null");
185
197
  return parseEpisodeSummaryDraft(result);
186
198
  };
187
- const findExistingEpisodeSummary = async (episodeId, scope) => {
188
- const result = await qdrant.qdrantRequest("POST", `/collections/${qdrant.collection}/points/scroll`, {
199
+ const findExistingEpisodeSummary = async (episodeId, scope, destination) => {
200
+ const collection = qdrant.collectionForDestination(destination);
201
+ const result = await qdrant.qdrantRequest("POST", `/collections/${collection}/points/scroll`, {
189
202
  filter: buildEpisodeSummaryFilter(episodeId, scope),
190
203
  limit: 1,
191
204
  with_payload: true,
192
- });
205
+ }, destination);
193
206
  return result.result?.points?.[0] ?? null;
194
207
  };
195
208
  export const updateEpisodeSummary = async (input) => {
196
209
  if (!input.segment.transcript.trim()) {
197
210
  return { action: "skipped", reason: "empty_transcript", episodeId: input.segment.episode_id };
198
211
  }
199
- const existing = await findExistingEpisodeSummary(input.segment.episode_id, input.scope);
212
+ const destination = qdrant.resolveDestination({
213
+ content: input.segment.transcript,
214
+ entities: [
215
+ input.sessionId,
216
+ ...(input.segment.workstream_key ? [input.segment.workstream_key] : []),
217
+ ],
218
+ metadata: {
219
+ session_id: input.sessionId,
220
+ episode_id: input.segment.episode_id,
221
+ ...(input.segment.workstream_key ? { workstream_key: input.segment.workstream_key } : {}),
222
+ memory_subtype: "episode",
223
+ kind: "summary",
224
+ origin_interface: "daemon",
225
+ origin_agent_type: "daemon",
226
+ },
227
+ });
228
+ const existing = await findExistingEpisodeSummary(input.segment.episode_id, input.scope, destination.name);
200
229
  const draft = await summarizeEpisodeTranscript({
201
230
  transcript: input.segment.transcript,
202
231
  sessionId: input.sessionId,
@@ -222,6 +251,7 @@ export const updateEpisodeSummary = async (input) => {
222
251
  scope: input.scope,
223
252
  now,
224
253
  existing,
254
+ config: input.config,
225
255
  redactionOptions: {
226
256
  enabled: true,
227
257
  redactPii: false,
@@ -229,13 +259,14 @@ export const updateEpisodeSummary = async (input) => {
229
259
  });
230
260
  const vector = await qdrant.embed(String(payload.content));
231
261
  const factId = existing?.id ?? randomUUID();
232
- await qdrant.qdrantRequest("PUT", `/collections/${qdrant.collection}/points`, {
262
+ await qdrant.qdrantRequest("PUT", `/collections/${destination.collection}/points`, {
233
263
  points: [{ id: factId, vector, payload }],
234
- });
264
+ }, destination.name);
235
265
  return {
236
266
  action: existing ? "updated" : "stored",
237
267
  factId,
238
268
  episodeId: input.segment.episode_id,
269
+ destination: destination.name,
239
270
  workstreamKey: resolved.key,
240
271
  };
241
272
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Events-based memory extraction — reads supported coding-agent transcripts,
3
- * extracts facts via LLM, and stores them in Qdrant with source: "system".
3
+ * extracts facts via LLM, and stores them in Qdrant with daemon origin metadata.
4
4
  *
5
5
  * Uses a JSON file for extraction state (high-water byte offsets) instead of SQLite.
6
6
  * Copilot session detection uses lock files. Claude Code detection uses
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Events-based memory extraction — reads supported coding-agent transcripts,
3
- * extracts facts via LLM, and stores them in Qdrant with source: "system".
3
+ * extracts facts via LLM, and stores them in Qdrant with daemon origin metadata.
4
4
  *
5
5
  * Uses a JSON file for extraction state (high-water byte offsets) instead of SQLite.
6
6
  * Copilot session detection uses lock files. Claude Code detection uses
@@ -19,7 +19,7 @@ import { CAPTURE_POLICY_VERSION, CAPTURE_TRIGGERS, DEFAULT_CAPTURE_CONTEXT, QUAL
19
19
  import { shouldSummarizeEvents, updateSessionSummary } from "./session-summary.js";
20
20
  import { redactStorageText } from "../privacy/redaction.js";
21
21
  import { compareSubtype, hasTypedToken, verifyGrounding, verifyVolatilityCoherence } from "./extraction-rules.js";
22
- import { resolveActorIdentity } from "../provenance/actor.js";
22
+ import { buildOperationOrigin } from "../provenance/origin.js";
23
23
  import { discoverClaudeTranscriptMappings, discoverCopilotTranscriptMappings, extractionStateKey, readNewTranscriptEvents, transcriptLabel, } from "./transcript-sources.js";
24
24
  // ── Module state ─────────────────────────────────────────────────────────────
25
25
  let logFn = (() => { });
@@ -415,11 +415,6 @@ const storeFacts = async (facts, sessionId, config, source) => {
415
415
  };
416
416
  if (source)
417
417
  baseMeta.extraction_source = source;
418
- const actor = resolveActorIdentity({ config });
419
- if (actor.actor_label)
420
- baseMeta.actor_label = actor.actor_label;
421
- if (actor.source)
422
- baseMeta.actor_source = actor.source;
423
418
  let stored = 0;
424
419
  for (const fact of facts) {
425
420
  const redactedContent = redactStorageText(fact.content);
@@ -429,12 +424,34 @@ const storeFacts = async (facts, sessionId, config, source) => {
429
424
  entities: fact.entities.map((entity) => entity.toLowerCase()),
430
425
  };
431
426
  const hash = contentHash(sanitizedFact.content);
427
+ const routeInput = {
428
+ content: sanitizedFact.content,
429
+ entities: sanitizedFact.entities,
430
+ metadata: {
431
+ ...baseMeta,
432
+ ...(fact.repo ? { repo: fact.repo } : {}),
433
+ ...(fact.branch ? { branch: fact.branch } : {}),
434
+ ...(fact.task_key ? { task_key: fact.task_key } : {}),
435
+ ...(fact.workstream_key ? { workstream_key: fact.workstream_key } : {}),
436
+ category: fact.category,
437
+ },
438
+ };
432
439
  try {
433
- const dedup = await qdrant.dedupCheck(sanitizedFact.content, hash);
440
+ const dedup = await qdrant.dedupCheck(sanitizedFact.content, hash, undefined, undefined, routeInput);
434
441
  if (dedup.action === "skip") {
435
442
  // Reinforce existing fact
436
443
  if (dedup.existingId) {
437
- await qdrant.reinforceFact(dedup.existingId, dedup.existingCount || 1);
444
+ await qdrant.reinforceFact(dedup.existingId, dedup.existingCount || 1, dedup.destination, buildOperationOrigin({
445
+ interface: "daemon",
446
+ action: "reinforce",
447
+ subsystem: "extraction",
448
+ config,
449
+ metadata: {
450
+ session_id: sessionId,
451
+ ...(source ? { transcript_source: source } : {}),
452
+ dedup_action: dedup.action,
453
+ },
454
+ }));
438
455
  }
439
456
  continue;
440
457
  }
@@ -444,6 +461,7 @@ const storeFacts = async (facts, sessionId, config, source) => {
444
461
  const contradiction = await detectContradiction(sanitizedFact, config, {
445
462
  sessionId,
446
463
  workstreamKey: sanitizedFact.workstream_key ?? undefined,
464
+ destination: dedup.destination,
447
465
  });
448
466
  if (contradiction.contradiction && contradiction.existingId) {
449
467
  logFn("INFO", `Extraction: contradiction detected for "${fact.content.slice(0, 60)}..." vs ${contradiction.existingId}: ${contradiction.reason}`);
@@ -533,7 +551,7 @@ const storeFacts = async (facts, sessionId, config, source) => {
533
551
  // Downgrade to candidate with reduced confidence rather than dropping
534
552
  // outright: similarity is a soft signal, not a hard reject.
535
553
  try {
536
- const badMatch = await qdrant.badExemplarCheck(sanitizedFact.content);
554
+ const badMatch = await qdrant.badExemplarCheck(sanitizedFact.content, undefined, routeInput);
537
555
  if (badMatch && badMatch.score >= 0.85) {
538
556
  effectiveConfidence = clamp01(effectiveConfidence - 0.2);
539
557
  reviewStatus = "candidate";
@@ -553,7 +571,6 @@ const storeFacts = async (facts, sessionId, config, source) => {
553
571
  domain: DEFAULT_CAPTURE_CONTEXT.domain,
554
572
  memory_subtype: effectiveSubtype,
555
573
  entities: sanitizedFact.entities,
556
- source: "system",
557
574
  kind: "fact",
558
575
  confidence: effectiveConfidence,
559
576
  importance: fact.importance,
@@ -571,17 +588,35 @@ const storeFacts = async (facts, sessionId, config, source) => {
571
588
  task_key: fact.task_key,
572
589
  workstream_key: fact.workstream_key,
573
590
  metadata: factMeta,
591
+ origin: buildOperationOrigin({
592
+ interface: "daemon",
593
+ action: "create",
594
+ subsystem: "extraction",
595
+ config,
596
+ metadata: {
597
+ session_id: sessionId,
598
+ ...(source ? { transcript_source: source } : {}),
599
+ capture_policy_version: CAPTURE_POLICY_VERSION,
600
+ },
601
+ }),
574
602
  };
575
- if (actor.actor_id) {
576
- storePayload.actor_id = actor.actor_id;
577
- }
578
603
  if (dedup.action === "supersede" && dedup.existingId) {
579
- const newId = await qdrant.storeFact(storePayload);
580
- await qdrant.supersedeFact(dedup.existingId, newId);
604
+ const newId = await qdrant.storeFact(storePayload, routeInput);
605
+ await qdrant.supersedeFact(dedup.existingId, newId, dedup.destination, buildOperationOrigin({
606
+ interface: "daemon",
607
+ action: "supersede",
608
+ subsystem: "extraction",
609
+ config,
610
+ metadata: {
611
+ session_id: sessionId,
612
+ new_fact_id: newId,
613
+ ...(source ? { transcript_source: source } : {}),
614
+ },
615
+ }));
581
616
  stored++;
582
617
  }
583
618
  else {
584
- await qdrant.storeFact(storePayload);
619
+ await qdrant.storeFact(storePayload, routeInput);
585
620
  stored++;
586
621
  }
587
622
  }
@@ -7,9 +7,12 @@
7
7
  * `qdrant_api_key` is optional — leave unset for unauthenticated local /
8
8
  * self-hosted instances.
9
9
  */
10
+ import { type Destination } from "../config.js";
10
11
  import { embed } from "../llm/index.js";
11
12
  import type { InitEmbeddingInput } from "../llm/index.js";
13
+ import { type RoutingInput } from "../routing.js";
12
14
  import { type RedactionSummary } from "../privacy/redaction.js";
15
+ import { type OperationOrigin } from "../provenance/origin.js";
13
16
  export type LogFn = (level: string, ...args: unknown[]) => void;
14
17
  export interface QdrantPayload {
15
18
  content: string;
@@ -18,10 +21,15 @@ export interface QdrantPayload {
18
21
  kind: string;
19
22
  layer?: string | null;
20
23
  memory_subtype?: string | null;
24
+ origin?: OperationOrigin;
25
+ last_operation_origin?: OperationOrigin;
26
+ /** @deprecated Origin is canonical for new writes. */
21
27
  workspace_id?: string;
28
+ /** @deprecated Origin is canonical for new writes. */
22
29
  actor_id?: string;
23
30
  entities: string[];
24
- source: string;
31
+ /** @deprecated Origin is canonical for new writes. */
32
+ source?: string;
25
33
  confidence: number;
26
34
  importance: number;
27
35
  content_hash: string;
@@ -65,11 +73,15 @@ export interface StoreFact {
65
73
  layer?: string | null;
66
74
  memory_subtype?: string | null;
67
75
  entities: string[];
76
+ origin?: OperationOrigin;
77
+ last_operation_origin?: OperationOrigin;
78
+ /** @deprecated Origin is canonical for new writes. */
68
79
  source?: string;
69
80
  confidence?: number;
70
81
  importance?: number;
71
82
  content_hash: string;
72
83
  workspace_id?: string;
84
+ /** @deprecated Origin is canonical for new writes. */
73
85
  actor_id?: string;
74
86
  metadata?: Record<string, string | number | boolean | null>;
75
87
  session_id?: string | null;
@@ -100,6 +112,7 @@ export interface StoreFact {
100
112
  }
101
113
  export interface QdrantSearchResult {
102
114
  id: string;
115
+ destination?: string;
103
116
  score: number;
104
117
  content: string;
105
118
  category: string;
@@ -110,6 +123,7 @@ export interface QdrantSearchResult {
110
123
  }
111
124
  export interface QdrantScrollResult {
112
125
  id: string;
126
+ destination?: string;
113
127
  content: string;
114
128
  category: string;
115
129
  entities: string[];
@@ -151,6 +165,7 @@ export interface QdrantScrollFilters {
151
165
  export type DedupAction = "insert" | "skip" | "supersede";
152
166
  export interface DedupResult {
153
167
  action: DedupAction;
168
+ destination?: string;
154
169
  existingId?: string;
155
170
  existingCount?: number;
156
171
  score?: number;
@@ -162,16 +177,23 @@ export interface DedupThresholds {
162
177
  declare let collection: string;
163
178
  declare const setLogger: (fn: LogFn) => void;
164
179
  declare const setEmbeddingConfig: (overrides?: Partial<InitEmbeddingInput>) => void;
180
+ type DestinationRef = Destination | string | null | undefined;
181
+ declare const resolveDestination: (input?: RoutingInput) => Destination;
165
182
  declare const init: () => boolean;
166
183
  declare const isReady: () => boolean;
167
184
  declare const ensureCollection: () => Promise<void>;
168
- declare const qdrantRequest: (method: string, urlPath: string, body?: unknown) => Promise<Record<string, unknown>>;
169
- declare const searchFacts: (query: string, filters?: QdrantSearchFilters, limit?: number) => Promise<QdrantSearchResult[]>;
170
- declare const scrollFacts: (filters?: QdrantScrollFilters, limit?: number) => Promise<QdrantScrollResult[]>;
171
- declare const storeFact: (fact: StoreFact) => Promise<string>;
172
- declare const supersedeFact: (oldFactId: string, newFactId: string) => Promise<void>;
173
- declare const reinforceFact: (factId: string, currentCount: number) => Promise<void>;
174
- declare const dedupCheck: (content: string, contentHashVal: string, { exactThreshold, supersedeThreshold }?: DedupThresholds, workspaceId?: string) => Promise<DedupResult>;
185
+ declare const qdrantRequest: (method: string, urlPath: string, body?: unknown, destinationRef?: DestinationRef) => Promise<Record<string, unknown>>;
186
+ declare const collectionForDestination: (destinationRef?: DestinationRef) => string;
187
+ declare const destinationNames: () => string[];
188
+ declare const readyDestinations: () => Destination[];
189
+ declare const activeDestinations: () => Destination[];
190
+ declare const searchFacts: (query: string, filters?: QdrantSearchFilters, limit?: number, destinationRef?: DestinationRef) => Promise<QdrantSearchResult[]>;
191
+ declare const scrollFacts: (filters?: QdrantScrollFilters, limit?: number, destinationRef?: DestinationRef) => Promise<QdrantScrollResult[]>;
192
+ declare const scrollFactsAcrossDestinations: (filters?: QdrantScrollFilters, limit?: number) => Promise<QdrantScrollResult[]>;
193
+ declare const storeFact: (fact: StoreFact, routeInput?: RoutingInput) => Promise<string>;
194
+ declare const supersedeFact: (oldFactId: string, newFactId: string, destinationRef?: DestinationRef, origin?: OperationOrigin) => Promise<void>;
195
+ declare const reinforceFact: (factId: string, currentCount: number, destinationRef?: DestinationRef, origin?: OperationOrigin) => Promise<void>;
196
+ declare const dedupCheck: (content: string, contentHashVal: string, { exactThreshold, supersedeThreshold }?: DedupThresholds, workspaceId?: string, routeInput?: RoutingInput) => Promise<DedupResult>;
175
197
  /**
176
198
  * Check whether incoming content is similar to any fact previously marked as a
177
199
  * bad exemplar (via memory_forget). Returns the top similarity score, or null
@@ -181,10 +203,10 @@ declare const dedupCheck: (content: string, contentHashVal: string, { exactThres
181
203
  * No hardcoded vocabulary — purely embedding-similarity based, and the
182
204
  * exemplar set grows organically every time a user calls memory_forget.
183
205
  */
184
- declare const badExemplarCheck: (content: string, workspaceId?: string) => Promise<{
206
+ declare const badExemplarCheck: (content: string, workspaceId?: string, routeInput?: RoutingInput) => Promise<{
185
207
  score: number;
186
208
  exemplarId: string;
187
209
  reason?: string;
188
210
  } | null>;
189
- export { init, isReady, ensureCollection, setLogger, setEmbeddingConfig, qdrantRequest, embed, searchFacts, scrollFacts, storeFact, supersedeFact, reinforceFact, dedupCheck, badExemplarCheck, collection, };
211
+ export { init, isReady, ensureCollection, setLogger, setEmbeddingConfig, qdrantRequest, resolveDestination, collectionForDestination, destinationNames, readyDestinations, activeDestinations, embed, searchFacts, scrollFacts, scrollFactsAcrossDestinations, storeFact, supersedeFact, reinforceFact, dedupCheck, badExemplarCheck, collection, };
190
212
  //# sourceMappingURL=qdrant.d.ts.map