bikky 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/CODE_OF_CONDUCT.md +80 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +48 -14
- package/SECURITY.md +58 -0
- package/SUPPORT.md +23 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +72 -0
- package/dist/daemon/extraction.d.ts +12 -2
- package/dist/daemon/extraction.js +85 -133
- package/dist/daemon/transcript-sources.d.ts +26 -0
- package/dist/daemon/transcript-sources.js +193 -0
- package/dist/daemon/watcher.d.ts +3 -2
- package/dist/daemon/watcher.js +51 -2
- package/dist/install.d.ts +9 -1
- package/dist/install.js +62 -34
- package/dist/mcp/api.d.ts +4 -3
- package/dist/mcp/api.js +4 -3
- package/dist/mcp/tools.js +317 -93
- package/dist/search-scope.d.ts +24 -0
- package/dist/search-scope.js +174 -0
- package/docs/config/fully-hosted.md +1 -1
- package/docs/config/hosted-models.md +1 -1
- package/docs/configuration.md +34 -5
- package/docs/privacy-first.md +140 -0
- package/package.json +27 -5
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -9
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -576
- package/dist/config.test.js.map +0 -1
- package/dist/daemon/capture-policy.d.ts.map +0 -1
- package/dist/daemon/capture-policy.js.map +0 -1
- package/dist/daemon/capture-policy.test.d.ts +0 -2
- package/dist/daemon/capture-policy.test.d.ts.map +0 -1
- package/dist/daemon/capture-policy.test.js +0 -48
- package/dist/daemon/capture-policy.test.js.map +0 -1
- package/dist/daemon/consolidation.d.ts.map +0 -1
- package/dist/daemon/consolidation.js.map +0 -1
- package/dist/daemon/entity-typing.d.ts.map +0 -1
- package/dist/daemon/entity-typing.js.map +0 -1
- package/dist/daemon/entity-typing.test.d.ts +0 -2
- package/dist/daemon/entity-typing.test.d.ts.map +0 -1
- package/dist/daemon/entity-typing.test.js +0 -50
- package/dist/daemon/entity-typing.test.js.map +0 -1
- package/dist/daemon/episode-summary.d.ts.map +0 -1
- package/dist/daemon/episode-summary.js.map +0 -1
- package/dist/daemon/episode-summary.test.d.ts +0 -2
- package/dist/daemon/episode-summary.test.d.ts.map +0 -1
- package/dist/daemon/episode-summary.test.js +0 -104
- package/dist/daemon/episode-summary.test.js.map +0 -1
- package/dist/daemon/extraction-quality.test.d.ts +0 -2
- package/dist/daemon/extraction-quality.test.d.ts.map +0 -1
- package/dist/daemon/extraction-quality.test.js +0 -283
- package/dist/daemon/extraction-quality.test.js.map +0 -1
- package/dist/daemon/extraction-rules.d.ts.map +0 -1
- package/dist/daemon/extraction-rules.js.map +0 -1
- package/dist/daemon/extraction-rules.test.d.ts +0 -2
- package/dist/daemon/extraction-rules.test.d.ts.map +0 -1
- package/dist/daemon/extraction-rules.test.js +0 -203
- package/dist/daemon/extraction-rules.test.js.map +0 -1
- package/dist/daemon/extraction.d.ts.map +0 -1
- package/dist/daemon/extraction.js.map +0 -1
- package/dist/daemon/extraction.test.d.ts +0 -2
- package/dist/daemon/extraction.test.d.ts.map +0 -1
- package/dist/daemon/extraction.test.js +0 -225
- package/dist/daemon/extraction.test.js.map +0 -1
- package/dist/daemon/index.d.ts.map +0 -1
- package/dist/daemon/index.js.map +0 -1
- package/dist/daemon/loop.d.ts.map +0 -1
- package/dist/daemon/loop.js.map +0 -1
- package/dist/daemon/loop.test.d.ts +0 -2
- package/dist/daemon/loop.test.d.ts.map +0 -1
- package/dist/daemon/loop.test.js +0 -85
- package/dist/daemon/loop.test.js.map +0 -1
- package/dist/daemon/maintenance-state.d.ts.map +0 -1
- package/dist/daemon/maintenance-state.js.map +0 -1
- package/dist/daemon/maintenance-state.test.d.ts +0 -2
- package/dist/daemon/maintenance-state.test.d.ts.map +0 -1
- package/dist/daemon/maintenance-state.test.js +0 -56
- package/dist/daemon/maintenance-state.test.js.map +0 -1
- package/dist/daemon/qdrant.d.ts.map +0 -1
- package/dist/daemon/qdrant.js.map +0 -1
- package/dist/daemon/qdrant.test.d.ts +0 -8
- package/dist/daemon/qdrant.test.d.ts.map +0 -1
- package/dist/daemon/qdrant.test.js +0 -265
- package/dist/daemon/qdrant.test.js.map +0 -1
- package/dist/daemon/relations-vocab.d.ts.map +0 -1
- package/dist/daemon/relations-vocab.js.map +0 -1
- package/dist/daemon/relations-vocab.test.d.ts +0 -2
- package/dist/daemon/relations-vocab.test.d.ts.map +0 -1
- package/dist/daemon/relations-vocab.test.js +0 -69
- package/dist/daemon/relations-vocab.test.js.map +0 -1
- package/dist/daemon/relations.d.ts.map +0 -1
- package/dist/daemon/relations.js.map +0 -1
- package/dist/daemon/relations.test.d.ts +0 -2
- package/dist/daemon/relations.test.d.ts.map +0 -1
- package/dist/daemon/relations.test.js +0 -36
- package/dist/daemon/relations.test.js.map +0 -1
- package/dist/daemon/session-index.d.ts.map +0 -1
- package/dist/daemon/session-index.js.map +0 -1
- package/dist/daemon/session-index.test.d.ts +0 -2
- package/dist/daemon/session-index.test.d.ts.map +0 -1
- package/dist/daemon/session-index.test.js +0 -60
- package/dist/daemon/session-index.test.js.map +0 -1
- package/dist/daemon/session-summary.d.ts.map +0 -1
- package/dist/daemon/session-summary.js.map +0 -1
- package/dist/daemon/session-summary.test.d.ts +0 -2
- package/dist/daemon/session-summary.test.d.ts.map +0 -1
- package/dist/daemon/session-summary.test.js +0 -162
- package/dist/daemon/session-summary.test.js.map +0 -1
- package/dist/daemon/staleness.d.ts.map +0 -1
- package/dist/daemon/staleness.js.map +0 -1
- package/dist/daemon/staleness.test.d.ts +0 -7
- package/dist/daemon/staleness.test.d.ts.map +0 -1
- package/dist/daemon/staleness.test.js +0 -128
- package/dist/daemon/staleness.test.js.map +0 -1
- package/dist/daemon/watcher-health.d.ts.map +0 -1
- package/dist/daemon/watcher-health.js.map +0 -1
- package/dist/daemon/watcher-health.test.d.ts +0 -5
- package/dist/daemon/watcher-health.test.d.ts.map +0 -1
- package/dist/daemon/watcher-health.test.js +0 -119
- package/dist/daemon/watcher-health.test.js.map +0 -1
- package/dist/daemon/watcher.d.ts.map +0 -1
- package/dist/daemon/watcher.js.map +0 -1
- package/dist/daemon/watcher.test.d.ts +0 -9
- package/dist/daemon/watcher.test.d.ts.map +0 -1
- package/dist/daemon/watcher.test.js +0 -204
- package/dist/daemon/watcher.test.js.map +0 -1
- package/dist/daemon/workstream-resolver.d.ts.map +0 -1
- package/dist/daemon/workstream-resolver.js.map +0 -1
- package/dist/daemon/workstream-resolver.test.d.ts +0 -2
- package/dist/daemon/workstream-resolver.test.d.ts.map +0 -1
- package/dist/daemon/workstream-resolver.test.js +0 -128
- package/dist/daemon/workstream-resolver.test.js.map +0 -1
- package/dist/daemon/workstream-summary.d.ts.map +0 -1
- package/dist/daemon/workstream-summary.js.map +0 -1
- package/dist/daemon/workstream-summary.test.d.ts +0 -2
- package/dist/daemon/workstream-summary.test.d.ts.map +0 -1
- package/dist/daemon/workstream-summary.test.js +0 -89
- package/dist/daemon/workstream-summary.test.js.map +0 -1
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -1
- package/dist/install.test.d.ts +0 -9
- package/dist/install.test.d.ts.map +0 -1
- package/dist/install.test.js +0 -126
- package/dist/install.test.js.map +0 -1
- package/dist/lib/qdrant-client.d.ts.map +0 -1
- package/dist/lib/qdrant-client.js.map +0 -1
- package/dist/lib/qdrant-client.test.d.ts +0 -8
- package/dist/lib/qdrant-client.test.d.ts.map +0 -1
- package/dist/lib/qdrant-client.test.js +0 -274
- package/dist/lib/qdrant-client.test.js.map +0 -1
- package/dist/lib/qdrant-pool.d.ts.map +0 -1
- package/dist/lib/qdrant-pool.js.map +0 -1
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js.map +0 -1
- package/dist/lifecycle.test.d.ts +0 -8
- package/dist/lifecycle.test.d.ts.map +0 -1
- package/dist/lifecycle.test.js +0 -74
- package/dist/lifecycle.test.js.map +0 -1
- package/dist/llm/embedding/index.d.ts.map +0 -1
- package/dist/llm/embedding/index.js.map +0 -1
- package/dist/llm/embedding/index.test.d.ts +0 -8
- package/dist/llm/embedding/index.test.d.ts.map +0 -1
- package/dist/llm/embedding/index.test.js +0 -100
- package/dist/llm/embedding/index.test.js.map +0 -1
- package/dist/llm/embedding/providers/bedrock.d.ts.map +0 -1
- package/dist/llm/embedding/providers/bedrock.js.map +0 -1
- package/dist/llm/embedding/providers/bedrock.test.d.ts +0 -2
- package/dist/llm/embedding/providers/bedrock.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/bedrock.test.js +0 -24
- package/dist/llm/embedding/providers/bedrock.test.js.map +0 -1
- package/dist/llm/embedding/providers/index.d.ts.map +0 -1
- package/dist/llm/embedding/providers/index.js.map +0 -1
- package/dist/llm/embedding/providers/ollama.d.ts.map +0 -1
- package/dist/llm/embedding/providers/ollama.js.map +0 -1
- package/dist/llm/embedding/providers/ollama.test.d.ts +0 -2
- package/dist/llm/embedding/providers/ollama.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/ollama.test.js +0 -54
- package/dist/llm/embedding/providers/ollama.test.js.map +0 -1
- package/dist/llm/embedding/providers/openai.d.ts.map +0 -1
- package/dist/llm/embedding/providers/openai.js.map +0 -1
- package/dist/llm/embedding/providers/openai.test.d.ts +0 -2
- package/dist/llm/embedding/providers/openai.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/openai.test.js +0 -48
- package/dist/llm/embedding/providers/openai.test.js.map +0 -1
- package/dist/llm/embedding/providers/portkey.d.ts.map +0 -1
- package/dist/llm/embedding/providers/portkey.js.map +0 -1
- package/dist/llm/embedding/providers/portkey.test.d.ts +0 -2
- package/dist/llm/embedding/providers/portkey.test.d.ts.map +0 -1
- package/dist/llm/embedding/providers/portkey.test.js +0 -56
- package/dist/llm/embedding/providers/portkey.test.js.map +0 -1
- package/dist/llm/embedding/registry.d.ts.map +0 -1
- package/dist/llm/embedding/registry.js.map +0 -1
- package/dist/llm/embedding/registry.test.d.ts +0 -7
- package/dist/llm/embedding/registry.test.d.ts.map +0 -1
- package/dist/llm/embedding/registry.test.js +0 -68
- package/dist/llm/embedding/registry.test.js.map +0 -1
- package/dist/llm/embedding/types.d.ts.map +0 -1
- package/dist/llm/embedding/types.js.map +0 -1
- package/dist/llm/errors.d.ts.map +0 -1
- package/dist/llm/errors.js.map +0 -1
- package/dist/llm/errors.test.d.ts +0 -2
- package/dist/llm/errors.test.d.ts.map +0 -1
- package/dist/llm/errors.test.js +0 -103
- package/dist/llm/errors.test.js.map +0 -1
- package/dist/llm/fetch.d.ts.map +0 -1
- package/dist/llm/fetch.js.map +0 -1
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/index.js.map +0 -1
- package/dist/llm/inference/index.d.ts.map +0 -1
- package/dist/llm/inference/index.js.map +0 -1
- package/dist/llm/inference/index.test.d.ts +0 -6
- package/dist/llm/inference/index.test.d.ts.map +0 -1
- package/dist/llm/inference/index.test.js +0 -150
- package/dist/llm/inference/index.test.js.map +0 -1
- package/dist/llm/inference/providers/bedrock.d.ts.map +0 -1
- package/dist/llm/inference/providers/bedrock.js.map +0 -1
- package/dist/llm/inference/providers/bedrock.test.d.ts +0 -2
- package/dist/llm/inference/providers/bedrock.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/bedrock.test.js +0 -68
- package/dist/llm/inference/providers/bedrock.test.js.map +0 -1
- package/dist/llm/inference/providers/index.d.ts.map +0 -1
- package/dist/llm/inference/providers/index.js.map +0 -1
- package/dist/llm/inference/providers/ollama.d.ts.map +0 -1
- package/dist/llm/inference/providers/ollama.js.map +0 -1
- package/dist/llm/inference/providers/ollama.test.d.ts +0 -2
- package/dist/llm/inference/providers/ollama.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/ollama.test.js +0 -57
- package/dist/llm/inference/providers/ollama.test.js.map +0 -1
- package/dist/llm/inference/providers/openai.d.ts.map +0 -1
- package/dist/llm/inference/providers/openai.js.map +0 -1
- package/dist/llm/inference/providers/openai.test.d.ts +0 -2
- package/dist/llm/inference/providers/openai.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/openai.test.js +0 -82
- package/dist/llm/inference/providers/openai.test.js.map +0 -1
- package/dist/llm/inference/providers/portkey.d.ts.map +0 -1
- package/dist/llm/inference/providers/portkey.js.map +0 -1
- package/dist/llm/inference/providers/portkey.test.d.ts +0 -2
- package/dist/llm/inference/providers/portkey.test.d.ts.map +0 -1
- package/dist/llm/inference/providers/portkey.test.js +0 -48
- package/dist/llm/inference/providers/portkey.test.js.map +0 -1
- package/dist/llm/inference/registry.d.ts.map +0 -1
- package/dist/llm/inference/registry.js.map +0 -1
- package/dist/llm/inference/registry.test.d.ts +0 -6
- package/dist/llm/inference/registry.test.d.ts.map +0 -1
- package/dist/llm/inference/registry.test.js +0 -63
- package/dist/llm/inference/registry.test.js.map +0 -1
- package/dist/llm/inference/types.d.ts.map +0 -1
- package/dist/llm/inference/types.js.map +0 -1
- package/dist/llm/telemetry.d.ts.map +0 -1
- package/dist/llm/telemetry.js.map +0 -1
- package/dist/llm/telemetry.test.d.ts +0 -5
- package/dist/llm/telemetry.test.d.ts.map +0 -1
- package/dist/llm/telemetry.test.js +0 -89
- package/dist/llm/telemetry.test.js.map +0 -1
- package/dist/llm/types.d.ts.map +0 -1
- package/dist/llm/types.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/logger.test.d.ts +0 -5
- package/dist/logger.test.d.ts.map +0 -1
- package/dist/logger.test.js +0 -103
- package/dist/logger.test.js.map +0 -1
- package/dist/mcp/api.d.ts.map +0 -1
- package/dist/mcp/api.js.map +0 -1
- package/dist/mcp/helpers.d.ts.map +0 -1
- package/dist/mcp/helpers.js.map +0 -1
- package/dist/mcp/helpers.test.d.ts +0 -5
- package/dist/mcp/helpers.test.d.ts.map +0 -1
- package/dist/mcp/helpers.test.js +0 -530
- package/dist/mcp/helpers.test.js.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/taxonomy.d.ts.map +0 -1
- package/dist/mcp/taxonomy.js.map +0 -1
- package/dist/mcp/taxonomy.test.d.ts +0 -5
- package/dist/mcp/taxonomy.test.d.ts.map +0 -1
- package/dist/mcp/taxonomy.test.js +0 -215
- package/dist/mcp/taxonomy.test.js.map +0 -1
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js.map +0 -1
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js.map +0 -1
- package/dist/postinstall.d.ts.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/privacy/redaction.d.ts.map +0 -1
- package/dist/privacy/redaction.js.map +0 -1
- package/dist/privacy/redaction.test.d.ts +0 -2
- package/dist/privacy/redaction.test.d.ts.map +0 -1
- package/dist/privacy/redaction.test.js +0 -51
- package/dist/privacy/redaction.test.js.map +0 -1
- package/dist/prompts/brief.d.ts.map +0 -1
- package/dist/prompts/brief.js.map +0 -1
- package/dist/prompts/contradiction.d.ts.map +0 -1
- package/dist/prompts/contradiction.js.map +0 -1
- package/dist/prompts/distill.d.ts.map +0 -1
- package/dist/prompts/distill.js.map +0 -1
- package/dist/prompts/entity-typing.d.ts.map +0 -1
- package/dist/prompts/entity-typing.js.map +0 -1
- package/dist/prompts/episode-summary.d.ts.map +0 -1
- package/dist/prompts/episode-summary.js.map +0 -1
- package/dist/prompts/extraction.d.ts.map +0 -1
- package/dist/prompts/extraction.js.map +0 -1
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/prompts.test.d.ts +0 -8
- package/dist/prompts/prompts.test.d.ts.map +0 -1
- package/dist/prompts/prompts.test.js +0 -140
- package/dist/prompts/prompts.test.js.map +0 -1
- package/dist/prompts/relations.d.ts.map +0 -1
- package/dist/prompts/relations.js.map +0 -1
- package/dist/prompts/workstream-summary.d.ts.map +0 -1
- package/dist/prompts/workstream-summary.js.map +0 -1
- package/dist/provenance/actor.d.ts.map +0 -1
- package/dist/provenance/actor.js.map +0 -1
- package/dist/provenance/actor.test.d.ts +0 -2
- package/dist/provenance/actor.test.d.ts.map +0 -1
- package/dist/provenance/actor.test.js +0 -49
- package/dist/provenance/actor.test.js.map +0 -1
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js.map +0 -1
- package/dist/render.test.d.ts +0 -8
- package/dist/render.test.d.ts.map +0 -1
- package/dist/render.test.js +0 -244
- package/dist/render.test.js.map +0 -1
- package/dist/routing.d.ts.map +0 -1
- package/dist/routing.js.map +0 -1
- package/dist/routing.test.d.ts +0 -2
- package/dist/routing.test.d.ts.map +0 -1
- package/dist/routing.test.js +0 -79
- package/dist/routing.test.js.map +0 -1
- package/dist/status.d.ts.map +0 -1
- package/dist/status.js.map +0 -1
- package/dist/status.test.d.ts +0 -5
- package/dist/status.test.d.ts.map +0 -1
- package/dist/status.test.js +0 -203
- package/dist/status.test.js.map +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -8,6 +8,7 @@ import { contentHash, daysSince, lastActivityDate, computeCombinedScore, buildFi
|
|
|
8
8
|
import { ready, setupError, setReady, log, embed, getEmbeddingConfig, qdrantReq, ensureCollectionsAll, qdrantUpsert, qdrantSearch, qdrantScroll, qdrantSetPayload, qdrantGetPoints, rebuildPool, hasPool, listDestinations, resolveDest, findPointById, } from "./api.js";
|
|
9
9
|
import { DestinationNotFoundError } from "../routing.js";
|
|
10
10
|
import { saveConfig, loadConfig, EXTRACTION_HEALTH_PATH } from "../config.js";
|
|
11
|
+
import { availableSearchScopes, resolveSearchScope, SearchScopeNotFoundError, } from "../search-scope.js";
|
|
11
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
13
|
import { inspectWatcherPaths, formatIssue, repairSuspiciousWatcherPaths } from "../daemon/watcher-health.js";
|
|
13
14
|
import { normalizeActorId, resolveActorIdentity } from "../provenance/actor.js";
|
|
@@ -18,6 +19,7 @@ import { addRedactionPayload, combineRedactions, redactStorageText, } from "../p
|
|
|
18
19
|
const NUDGE_INTERVAL_MS = 10 * 60 * 1000;
|
|
19
20
|
const MEMORY_RECALL_DEFAULT_LIMIT = 10;
|
|
20
21
|
const MEMORY_RECALL_MAX_LIMIT = 50;
|
|
22
|
+
const searchScopeSchema = z.union([z.string(), z.array(z.string())]).optional();
|
|
21
23
|
let lastStoreTime = Date.now();
|
|
22
24
|
let heartbeatCount = 0;
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
@@ -67,6 +69,63 @@ function resolveDestOrError(input) {
|
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
function withDestination(point, destination) {
|
|
73
|
+
return { ...point, _destination: destination };
|
|
74
|
+
}
|
|
75
|
+
function structuredScopedFact(point) {
|
|
76
|
+
return {
|
|
77
|
+
...structuredFact(point),
|
|
78
|
+
destination: point._destination.name,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function formatScopedFact(point, includeDestination) {
|
|
82
|
+
const formatted = formatFact(point);
|
|
83
|
+
return includeDestination ? `[${point._destination.name}] ${formatted}` : formatted;
|
|
84
|
+
}
|
|
85
|
+
function resolveSearchScopeOrError(args) {
|
|
86
|
+
if (args.destination && args.search_scope !== undefined) {
|
|
87
|
+
return {
|
|
88
|
+
error: {
|
|
89
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
90
|
+
status: "ambiguous_search_scope",
|
|
91
|
+
message: "Use either destination for a single destination override or search_scope for routed/all/list search, not both.",
|
|
92
|
+
}, null, 2) }],
|
|
93
|
+
isError: true,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (args.destination) {
|
|
98
|
+
const resolved = resolveDestOrError({ ...args.input, destination: args.destination });
|
|
99
|
+
if (resolved.error)
|
|
100
|
+
return { error: resolved.error };
|
|
101
|
+
return {
|
|
102
|
+
scope: {
|
|
103
|
+
name: resolved.dest.name,
|
|
104
|
+
description: resolved.dest.description ?? `Search only the '${resolved.dest.name}' destination.`,
|
|
105
|
+
requested: resolved.dest.name,
|
|
106
|
+
destinations: [resolved.dest],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const cfg = loadConfig();
|
|
111
|
+
const dests = listDestinations();
|
|
112
|
+
try {
|
|
113
|
+
return { scope: resolveSearchScope(args.search_scope, cfg, dests, args.input) };
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
117
|
+
return {
|
|
118
|
+
error: {
|
|
119
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
120
|
+
status: e instanceof SearchScopeNotFoundError ? "search_scope_not_found" : "search_scope_error",
|
|
121
|
+
message: msg,
|
|
122
|
+
available_search_scopes: availableSearchScopes(cfg, dests),
|
|
123
|
+
}, null, 2) }],
|
|
124
|
+
isError: true,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
70
129
|
// Add actor identity payload fields. Workspace was removed in v0.4.0 — physical
|
|
71
130
|
// separation now happens via routing destinations (see routing.ts).
|
|
72
131
|
function addActorPayload(payload, actor, actorIdOverride) {
|
|
@@ -218,6 +277,7 @@ export function registerTools(mcp) {
|
|
|
218
277
|
"Use this when memory tools return a 'setup_required' error, or once at session start if you're not sure bikky is wired up. Reports which credentials are missing and includes onboarding instructions if anything is incomplete.",
|
|
219
278
|
"Read-only — safe to call any time.",
|
|
220
279
|
].join(" "), {}, async () => {
|
|
280
|
+
const cfg = loadConfig();
|
|
221
281
|
const dests = listDestinations();
|
|
222
282
|
const status = {
|
|
223
283
|
ready,
|
|
@@ -245,6 +305,7 @@ export function registerTools(mcp) {
|
|
|
245
305
|
} })(),
|
|
246
306
|
collection: d.collection,
|
|
247
307
|
default: d.default ?? false,
|
|
308
|
+
...(d.description ? { description: d.description } : {}),
|
|
248
309
|
connected: false,
|
|
249
310
|
collection_exists: false,
|
|
250
311
|
};
|
|
@@ -263,6 +324,10 @@ export function registerTools(mcp) {
|
|
|
263
324
|
destStatus.push(block);
|
|
264
325
|
}
|
|
265
326
|
status["destinations"] = destStatus;
|
|
327
|
+
status["default_search_scope"] = cfg.default_search_scope;
|
|
328
|
+
status["search_scopes"] = availableSearchScopes(cfg, dests);
|
|
329
|
+
status["search_scope_hint"] =
|
|
330
|
+
"Read/search tools accept search_scope: 'routed', 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Use destination only when you want an exact single-destination override.";
|
|
266
331
|
try {
|
|
267
332
|
await embed("test");
|
|
268
333
|
status["embedding_connected"] = true;
|
|
@@ -271,8 +336,11 @@ export function registerTools(mcp) {
|
|
|
271
336
|
// Watcher / extraction health (issue #58)
|
|
272
337
|
const warnings = [];
|
|
273
338
|
try {
|
|
274
|
-
const cfg = loadConfig();
|
|
275
339
|
status["watcher_path"] = cfg.watchers.copilot.path;
|
|
340
|
+
status["watcher_paths"] = {
|
|
341
|
+
copilot: cfg.watchers.copilot.path,
|
|
342
|
+
claude: cfg.watchers.claude.path,
|
|
343
|
+
};
|
|
276
344
|
for (const issue of inspectWatcherPaths(cfg)) {
|
|
277
345
|
warnings.push(formatIssue(issue));
|
|
278
346
|
}
|
|
@@ -284,17 +352,19 @@ export function registerTools(mcp) {
|
|
|
284
352
|
status["extraction_last_tick_at"] = health.last_tick_at ?? null;
|
|
285
353
|
status["extraction_last_active_session_at"] = health.last_active_session_at ?? null;
|
|
286
354
|
status["extraction_active_session_count"] = health.active_session_count ?? 0;
|
|
355
|
+
if (health.sources)
|
|
356
|
+
status["extraction_sources"] = health.sources;
|
|
287
357
|
if (health.last_active_session_at) {
|
|
288
358
|
const hours = (Date.now() - Date.parse(health.last_active_session_at)) / 3_600_000;
|
|
289
359
|
status["extraction_hours_since_active_session"] = Math.round(hours * 10) / 10;
|
|
290
360
|
if (hours > 6) {
|
|
291
|
-
warnings.push(`Watcher has not seen any active
|
|
361
|
+
warnings.push(`Watcher has not seen any active transcript sources for ${Math.round(hours)}h — ` +
|
|
292
362
|
`check watcher_path (${health.watcher_path ?? "unknown"}) and that the daemon is running.`);
|
|
293
363
|
}
|
|
294
364
|
}
|
|
295
365
|
else {
|
|
296
366
|
status["extraction_hours_since_active_session"] = null;
|
|
297
|
-
warnings.push("Daemon has never observed an active
|
|
367
|
+
warnings.push("Daemon has never observed an active transcript source — extraction may be stalled.");
|
|
298
368
|
}
|
|
299
369
|
}
|
|
300
370
|
else {
|
|
@@ -316,6 +386,25 @@ export function registerTools(mcp) {
|
|
|
316
386
|
}
|
|
317
387
|
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
318
388
|
});
|
|
389
|
+
// ── memory_search_scopes ─────────────────────────────────────────────────
|
|
390
|
+
mcp.tool("memory_search_scopes", [
|
|
391
|
+
"List the configured memory search scopes and destination descriptions.",
|
|
392
|
+
"Use this before memory_recall, memory_entity, or memory_relations when multiple destinations exist so you can choose the right search_scope.",
|
|
393
|
+
"Read-only — returns built-in scopes ('routed', 'all'), destination-name scopes, configured named scopes, and the default_search_scope.",
|
|
394
|
+
].join(" "), {}, async () => {
|
|
395
|
+
const cfg = loadConfig();
|
|
396
|
+
const dests = listDestinations();
|
|
397
|
+
return {
|
|
398
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
399
|
+
default_search_scope: cfg.default_search_scope,
|
|
400
|
+
scopes: availableSearchScopes(cfg, dests),
|
|
401
|
+
usage: {
|
|
402
|
+
search_scope: "Pass one scope name, 'routed', 'all', a destination name, a comma-separated destination list, or an array of destination names.",
|
|
403
|
+
destination: "Use this older parameter only for an exact single-destination override. Do not combine it with search_scope.",
|
|
404
|
+
},
|
|
405
|
+
}, null, 2) }],
|
|
406
|
+
};
|
|
407
|
+
});
|
|
319
408
|
// ── configure_credentials ───────────────────────────────────────────────
|
|
320
409
|
mcp.tool("configure_credentials", [
|
|
321
410
|
"Persist Qdrant and embedding credentials to ~/.bikky/config.json and bring the memory system online.",
|
|
@@ -719,6 +808,7 @@ export function registerTools(mcp) {
|
|
|
719
808
|
" 3. Conflict/replacement check — recall similar facts when you suspect new information may supersede an older fact. Deduplication during memory_store is automatic.",
|
|
720
809
|
"Combine the natural-language query with structured filters (category, domain, entity, date range, metadata) for tighter results.",
|
|
721
810
|
"If you have a known entity name and want everything about it, prefer memory_entity. For 'what does X own/use?' style questions, prefer memory_relations.",
|
|
811
|
+
"When multiple Qdrant destinations are configured, use search_scope to choose 'routed' (routing/default behavior), 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Call memory_search_scopes to inspect available scopes and descriptions.",
|
|
722
812
|
`By default output is human-readable text. Use output_format=json for machine-parseable results with separate results and related arrays. Default limit is ${MEMORY_RECALL_DEFAULT_LIMIT}; maximum effective limit is ${MEMORY_RECALL_MAX_LIMIT}.`,
|
|
723
813
|
].join("\n"), {
|
|
724
814
|
query: z.string().describe("Natural-language description of what you're looking for. Embedded and matched semantically — full sentences work better than keyword lists."),
|
|
@@ -727,7 +817,8 @@ export function registerTools(mcp) {
|
|
|
727
817
|
kind: z.string().optional().describe("Filter by kind: fact, summary, distilled, relation. Optional. Telemetry is excluded by default."),
|
|
728
818
|
memory_subtype: z.string().optional().describe("Filter by memory subtype (must be valid for the chosen kind). Optional."),
|
|
729
819
|
workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
|
|
730
|
-
destination: z.string().optional().describe("Optional destination override.
|
|
820
|
+
destination: z.string().optional().describe("Optional legacy single-destination override. Do not combine with search_scope. Prefer search_scope for routed/all/list search."),
|
|
821
|
+
search_scope: searchScopeSchema.describe("Optional read/search scope. Accepts 'routed', 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Omit to use config.default_search_scope."),
|
|
731
822
|
actor_id: z.string().optional().describe("Filter to facts captured by or associated with this stable actor identity. Optional."),
|
|
732
823
|
include_legacy_workspace: z.boolean().optional().describe("[Removed in v0.4.0] No-op."),
|
|
733
824
|
entity: z.string().optional().describe("Restrict to facts mentioning this entity (case-insensitive). For full entity context prefer memory_entity."),
|
|
@@ -743,22 +834,25 @@ export function registerTools(mcp) {
|
|
|
743
834
|
graph_depth: z.number().optional().default(0).describe("Entity-graph traversal depth. 0 = vector search only (fast, default). 1 = also surface up to ceil(limit / 2) extra 1-hop entity-related facts (slower; use when the user asks 'what's connected to X?'). In JSON output these are returned separately as related."),
|
|
744
835
|
output_format: z.enum(["text", "json"]).optional().default("text").describe("Response format. text = backward-compatible human-readable lines (default). json = parseable object with query, limit metadata, results, related, counts, and optional nudge."),
|
|
745
836
|
metadata_filter: z.record(z.string(), z.string()).optional().describe("Exact-match filter on the metadata map stored with each fact. All key/value pairs must match (AND logic)."),
|
|
746
|
-
}, async ({ query, category, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, actor_id, include_legacy_workspace: _include_legacy_workspace, entity, episode_id, workstream_key, task_key, repo, branch, review_status, since, until, limit, graph_depth, output_format, metadata_filter, }) => {
|
|
837
|
+
}, async ({ query, category, domain, kind, memory_subtype, workspace_id: _workspace_id, destination, search_scope, actor_id, include_legacy_workspace: _include_legacy_workspace, entity, episode_id, workstream_key, task_key, repo, branch, review_status, since, until, limit, graph_depth, output_format, metadata_filter, }) => {
|
|
747
838
|
const guard = requireReady();
|
|
748
839
|
if (guard)
|
|
749
840
|
return guard;
|
|
750
841
|
const requestedLimit = limit ?? MEMORY_RECALL_DEFAULT_LIMIT;
|
|
751
842
|
const effectiveLimit = clampRecallLimit(limit);
|
|
752
843
|
const actorFilter = resolveActorIdentity({ actorId: actor_id, useGitFallback: false });
|
|
753
|
-
const
|
|
844
|
+
const scopeResolved = resolveSearchScopeOrError({
|
|
754
845
|
destination,
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
846
|
+
search_scope,
|
|
847
|
+
input: routingInput({
|
|
848
|
+
content: query,
|
|
849
|
+
entities: entity ? [entity] : [],
|
|
850
|
+
metadata: metadata_filter,
|
|
851
|
+
}),
|
|
852
|
+
});
|
|
853
|
+
if (scopeResolved.error)
|
|
854
|
+
return scopeResolved.error;
|
|
855
|
+
const searchScope = scopeResolved.scope;
|
|
762
856
|
const redactedQuery = redactStorageText(query);
|
|
763
857
|
const vector = await embed(redactedQuery.text);
|
|
764
858
|
const normalizedKind = kind ? normalizeKind(kind) : undefined;
|
|
@@ -792,12 +886,42 @@ export function registerTools(mcp) {
|
|
|
792
886
|
metadata: metadata_filter,
|
|
793
887
|
excludeKinds: MEMORY_RECALL_EXCLUDED_KINDS,
|
|
794
888
|
});
|
|
795
|
-
const
|
|
796
|
-
|
|
889
|
+
const searchedDestinations = searchScope.destinations.map((dest) => dest.name);
|
|
890
|
+
const failedDestinations = [];
|
|
891
|
+
const scopedResults = [];
|
|
892
|
+
for (const dest of searchScope.destinations) {
|
|
893
|
+
try {
|
|
894
|
+
const results = await qdrantSearch(dest, vector, filter, effectiveLimit * 2);
|
|
895
|
+
scopedResults.push(...(results.result ?? []).map((point) => withDestination(point, dest)));
|
|
896
|
+
}
|
|
897
|
+
catch (e) {
|
|
898
|
+
failedDestinations.push({
|
|
899
|
+
destination: dest.name,
|
|
900
|
+
error: e instanceof Error ? e.message : String(e),
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (failedDestinations.length === searchScope.destinations.length && searchScope.destinations.length > 0) {
|
|
905
|
+
return {
|
|
906
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
907
|
+
status: "search_failed",
|
|
908
|
+
query: redactedQuery.text,
|
|
909
|
+
search_scope: searchScope.name,
|
|
910
|
+
searched_destinations: searchedDestinations,
|
|
911
|
+
failed_destinations: failedDestinations,
|
|
912
|
+
}, null, 2) }],
|
|
913
|
+
isError: true,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (scopedResults.length === 0) {
|
|
797
917
|
const nudge = buildMemoryNudge();
|
|
798
918
|
if (output_format === "json") {
|
|
799
919
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
800
920
|
query: redactedQuery.text,
|
|
921
|
+
search_scope: searchScope.name,
|
|
922
|
+
search_scope_description: searchScope.description,
|
|
923
|
+
searched_destinations: searchedDestinations,
|
|
924
|
+
failed_destinations: failedDestinations,
|
|
801
925
|
requested_limit: requestedLimit,
|
|
802
926
|
effective_limit: effectiveLimit,
|
|
803
927
|
max_limit: MEMORY_RECALL_MAX_LIMIT,
|
|
@@ -811,31 +935,61 @@ export function registerTools(mcp) {
|
|
|
811
935
|
...(redactedQuery.redacted ? { query_redaction: redactedQuery } : {}),
|
|
812
936
|
}, null, 2) }] };
|
|
813
937
|
}
|
|
814
|
-
const
|
|
938
|
+
const warning = failedDestinations.length > 0
|
|
939
|
+
? `\n\nSearch warnings: ${failedDestinations.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")}`
|
|
940
|
+
: "";
|
|
941
|
+
const text = nudge
|
|
942
|
+
? `No matching facts found.${warning}\n\n${nudge}`
|
|
943
|
+
: `No matching facts found.${warning}`;
|
|
815
944
|
return { content: [{ type: "text", text }] };
|
|
816
945
|
}
|
|
817
|
-
const ranked =
|
|
946
|
+
const ranked = scopedResults
|
|
818
947
|
.map((r) => ({ ...r, _combinedScore: computeCombinedScore(r) }))
|
|
819
948
|
.sort((a, b) => b._combinedScore - a._combinedScore)
|
|
820
949
|
.slice(0, effectiveLimit);
|
|
821
|
-
const
|
|
822
|
-
|
|
950
|
+
const includeDestination = searchScope.destinations.length > 1 || searchScope.name === "all";
|
|
951
|
+
const lines = ranked.map((r) => formatScopedFact(r, includeDestination));
|
|
952
|
+
const related = { points: [], errors: [] };
|
|
823
953
|
if ((graph_depth ?? 0) >= 1) {
|
|
824
|
-
|
|
954
|
+
const byDestination = new Map();
|
|
955
|
+
for (const point of ranked) {
|
|
956
|
+
const existing = byDestination.get(point._destination.name);
|
|
957
|
+
if (existing) {
|
|
958
|
+
existing.points.push(point);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
byDestination.set(point._destination.name, { dest: point._destination, points: [point] });
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
for (const entry of byDestination.values()) {
|
|
965
|
+
const traversal = await graphTraversal(entry.dest, entry.points, effectiveLimit);
|
|
966
|
+
related.points.push(...traversal.points.map((point) => withDestination(point, entry.dest)));
|
|
967
|
+
if (traversal.error) {
|
|
968
|
+
related.errors.push({ destination: entry.dest.name, error: traversal.error });
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
related.points = related.points.slice(0, Math.ceil(effectiveLimit / 2));
|
|
825
972
|
if (related.points.length > 0) {
|
|
826
973
|
lines.push("", "── Related (1-hop) ──");
|
|
827
|
-
lines.push(...related.points.map((r) =>
|
|
974
|
+
lines.push(...related.points.map((r) => formatScopedFact(r, includeDestination)));
|
|
828
975
|
}
|
|
829
|
-
|
|
830
|
-
lines.push("", `(graph traversal
|
|
976
|
+
if (related.errors.length > 0) {
|
|
977
|
+
lines.push("", `(graph traversal warnings: ${related.errors.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")})`);
|
|
831
978
|
}
|
|
832
979
|
}
|
|
980
|
+
if (failedDestinations.length > 0) {
|
|
981
|
+
lines.push("", `(search warnings: ${failedDestinations.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")})`);
|
|
982
|
+
}
|
|
833
983
|
const nudge = buildMemoryNudge();
|
|
834
984
|
if (nudge)
|
|
835
985
|
lines.push("", nudge);
|
|
836
986
|
if (output_format === "json") {
|
|
837
987
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
838
988
|
query: redactedQuery.text,
|
|
989
|
+
search_scope: searchScope.name,
|
|
990
|
+
search_scope_description: searchScope.description,
|
|
991
|
+
searched_destinations: searchedDestinations,
|
|
992
|
+
failed_destinations: failedDestinations,
|
|
839
993
|
requested_limit: requestedLimit,
|
|
840
994
|
effective_limit: effectiveLimit,
|
|
841
995
|
max_limit: MEMORY_RECALL_MAX_LIMIT,
|
|
@@ -843,9 +997,9 @@ export function registerTools(mcp) {
|
|
|
843
997
|
graph_depth: graph_depth ?? 0,
|
|
844
998
|
result_count: ranked.length,
|
|
845
999
|
related_count: related.points.length,
|
|
846
|
-
results: ranked.map((r) =>
|
|
847
|
-
related: related.points.map((r) =>
|
|
848
|
-
...(related.
|
|
1000
|
+
results: ranked.map((r) => structuredScopedFact(r)),
|
|
1001
|
+
related: related.points.map((r) => structuredScopedFact(r)),
|
|
1002
|
+
...(related.errors.length > 0 ? { graph_errors: related.errors } : {}),
|
|
849
1003
|
...(nudge ? { nudge } : {}),
|
|
850
1004
|
...(redactedQuery.redacted ? { query_redaction: redactedQuery } : {}),
|
|
851
1005
|
}, null, 2) }] };
|
|
@@ -861,76 +1015,112 @@ export function registerTools(mcp) {
|
|
|
861
1015
|
name: z.string().describe("Entity name (case-insensitive, e.g. 'qdrant', 'workspace_id'). Should match the lowercase canonical form used when facts were stored."),
|
|
862
1016
|
limit: z.number().optional().default(20).describe("Max facts to return (default 20). Relations are always returned in full, capped at 50 each direction."),
|
|
863
1017
|
workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
|
|
864
|
-
destination: z.string().optional().describe("Optional destination override.
|
|
1018
|
+
destination: z.string().optional().describe("Optional legacy single-destination override. Do not combine with search_scope."),
|
|
1019
|
+
search_scope: searchScopeSchema.describe("Optional read/search scope. Accepts 'routed', 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Omit to use config.default_search_scope."),
|
|
865
1020
|
include_legacy_workspace: z.boolean().optional().describe("[Removed in v0.4.0] No-op."),
|
|
866
|
-
}, async ({ name, limit, workspace_id: _workspace_id, destination, include_legacy_workspace: _include_legacy_workspace }) => {
|
|
1021
|
+
}, async ({ name, limit, workspace_id: _workspace_id, destination, search_scope, include_legacy_workspace: _include_legacy_workspace }) => {
|
|
867
1022
|
const guard = requireReady();
|
|
868
1023
|
if (guard)
|
|
869
1024
|
return guard;
|
|
870
1025
|
const entityName = name.toLowerCase();
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1026
|
+
const scopeResolved = resolveSearchScopeOrError({
|
|
1027
|
+
destination,
|
|
1028
|
+
search_scope,
|
|
1029
|
+
input: routingInput({ entities: [entityName] }),
|
|
1030
|
+
});
|
|
1031
|
+
if (scopeResolved.error)
|
|
1032
|
+
return scopeResolved.error;
|
|
1033
|
+
const searchScope = scopeResolved.scope;
|
|
1034
|
+
const effectiveLimit = Math.max(1, Math.trunc(limit ?? 20));
|
|
1035
|
+
const entityTypes = new Map();
|
|
1036
|
+
const factPoints = [];
|
|
1037
|
+
const relationPoints = [];
|
|
1038
|
+
const failures = [];
|
|
1039
|
+
for (const dest of searchScope.destinations) {
|
|
1040
|
+
// Look up the daemon-classified entity type, if any.
|
|
1041
|
+
try {
|
|
1042
|
+
const typeFilter = buildFilter({}) ?? { must: [] };
|
|
1043
|
+
typeFilter.must.push({ key: "kind", match: { value: "entity_type" } });
|
|
1044
|
+
typeFilter.must.push({ key: "entity_name", match: { value: entityName } });
|
|
1045
|
+
const typePoints = await qdrantScroll(dest, typeFilter, 1);
|
|
1046
|
+
const typePoint = typePoints.result?.points?.[0];
|
|
1047
|
+
const payload = typePoint?.payload;
|
|
1048
|
+
if (payload?.entity_type) {
|
|
1049
|
+
entityTypes.set(dest.name, String(payload.entity_type));
|
|
1050
|
+
}
|
|
886
1051
|
}
|
|
1052
|
+
catch {
|
|
1053
|
+
// Type lookup is best-effort — never fails the request.
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
const factsFilter = buildFilter({}) ?? { must: [] };
|
|
1057
|
+
factsFilter.must.push({ key: "entities", match: { value: entityName } });
|
|
1058
|
+
const facts = await qdrantScroll(dest, factsFilter, effectiveLimit);
|
|
1059
|
+
factPoints.push(...(facts.result?.points ?? []).map((point) => withDestination(point, dest)));
|
|
1060
|
+
const fromFilter = buildFilter({}) ?? { must: [] };
|
|
1061
|
+
fromFilter.must.push({ key: "from_entity", match: { value: entityName } });
|
|
1062
|
+
const relationsFrom = await qdrantScroll(dest, fromFilter, 50);
|
|
1063
|
+
const toFilter = buildFilter({}) ?? { must: [] };
|
|
1064
|
+
toFilter.must.push({ key: "to_entity", match: { value: entityName } });
|
|
1065
|
+
const relationsTo = await qdrantScroll(dest, toFilter, 50);
|
|
1066
|
+
relationPoints.push(...[
|
|
1067
|
+
...(relationsFrom.result?.points ?? []),
|
|
1068
|
+
...(relationsTo.result?.points ?? []),
|
|
1069
|
+
].map((point) => withDestination(point, dest)));
|
|
1070
|
+
}
|
|
1071
|
+
catch (e) {
|
|
1072
|
+
failures.push({ destination: dest.name, error: e instanceof Error ? e.message : String(e) });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (failures.length === searchScope.destinations.length && searchScope.destinations.length > 0) {
|
|
1076
|
+
return {
|
|
1077
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1078
|
+
status: "search_failed",
|
|
1079
|
+
entity: entityName,
|
|
1080
|
+
search_scope: searchScope.name,
|
|
1081
|
+
searched_destinations: searchScope.destinations.map((dest) => dest.name),
|
|
1082
|
+
failed_destinations: failures,
|
|
1083
|
+
}, null, 2) }],
|
|
1084
|
+
isError: true,
|
|
1085
|
+
};
|
|
887
1086
|
}
|
|
888
|
-
catch {
|
|
889
|
-
// Type lookup is best-effort — never fails the request.
|
|
890
|
-
}
|
|
891
|
-
const factsFilter = buildFilter({}) ?? { must: [] };
|
|
892
|
-
factsFilter.must.push({ key: "entities", match: { value: entityName } });
|
|
893
|
-
const facts = await qdrantScroll(dest, factsFilter, limit ?? 20);
|
|
894
|
-
const fromFilter = buildFilter({}) ?? { must: [] };
|
|
895
|
-
fromFilter.must.push({ key: "from_entity", match: { value: entityName } });
|
|
896
|
-
const relationsFrom = await qdrantScroll(dest, fromFilter, 50);
|
|
897
|
-
const toFilter = buildFilter({}) ?? { must: [] };
|
|
898
|
-
toFilter.must.push({ key: "to_entity", match: { value: entityName } });
|
|
899
|
-
const relationsTo = await qdrantScroll(dest, toFilter, 50);
|
|
900
1087
|
const output = [];
|
|
901
|
-
const
|
|
1088
|
+
const includeDestination = searchScope.destinations.length > 1 || searchScope.name === "all";
|
|
1089
|
+
const entityTypeValues = [...new Set(entityTypes.values())];
|
|
1090
|
+
const entityType = entityTypeValues.length === 1 ? entityTypeValues[0] : null;
|
|
902
1091
|
if (factPoints.length > 0) {
|
|
903
1092
|
const header = entityType
|
|
904
|
-
? `## Facts about ${name} [type: ${entityType}] (${factPoints.length})`
|
|
905
|
-
: `## Facts about ${name} (${factPoints.length})`;
|
|
1093
|
+
? `## Facts about ${name} [type: ${entityType}] (${Math.min(factPoints.length, effectiveLimit)})`
|
|
1094
|
+
: `## Facts about ${name} (${Math.min(factPoints.length, effectiveLimit)})`;
|
|
906
1095
|
output.push(header);
|
|
907
|
-
for (const p of factPoints) {
|
|
1096
|
+
for (const p of factPoints.slice(0, effectiveLimit)) {
|
|
908
1097
|
if (p.payload.category !== "relation") {
|
|
909
|
-
output.push(`- ${
|
|
1098
|
+
output.push(`- ${formatScopedFact(p, includeDestination)}`);
|
|
910
1099
|
}
|
|
911
1100
|
}
|
|
912
1101
|
}
|
|
913
1102
|
else if (entityType) {
|
|
914
1103
|
output.push(`## ${name} [type: ${entityType}]`);
|
|
915
1104
|
}
|
|
916
|
-
const allRelations = [
|
|
917
|
-
...(relationsFrom.result?.points ?? []),
|
|
918
|
-
...(relationsTo.result?.points ?? []),
|
|
919
|
-
];
|
|
920
1105
|
const seen = new Set();
|
|
921
|
-
const uniqueRelations =
|
|
922
|
-
|
|
1106
|
+
const uniqueRelations = relationPoints.filter((r) => {
|
|
1107
|
+
const key = `${r._destination.name}:${r.id}`;
|
|
1108
|
+
if (seen.has(key))
|
|
923
1109
|
return false;
|
|
924
|
-
seen.add(
|
|
1110
|
+
seen.add(key);
|
|
925
1111
|
return true;
|
|
926
1112
|
});
|
|
927
1113
|
if (uniqueRelations.length > 0) {
|
|
928
1114
|
output.push(`\n## Relations (${uniqueRelations.length})`);
|
|
929
1115
|
for (const r of uniqueRelations) {
|
|
930
1116
|
const p = r.payload;
|
|
931
|
-
|
|
1117
|
+
const prefix = includeDestination ? `[${r._destination.name}] ` : "";
|
|
1118
|
+
output.push(`- ${prefix}${p.from_entity} --[${p.relation_type}]--> ${p.to_entity}`);
|
|
932
1119
|
}
|
|
933
1120
|
}
|
|
1121
|
+
if (failures.length > 0) {
|
|
1122
|
+
output.push(`\nSearch warnings: ${failures.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")}`);
|
|
1123
|
+
}
|
|
934
1124
|
if (output.length === 0) {
|
|
935
1125
|
return { content: [{ type: "text", text: `No facts or relations found for '${name}'.` }] };
|
|
936
1126
|
}
|
|
@@ -946,50 +1136,84 @@ export function registerTools(mcp) {
|
|
|
946
1136
|
relation_type: z.string().optional().describe("Filter to a specific edge label (e.g. 'owns', 'uses', 'decided', 'prefers', 'works-on'). Optional."),
|
|
947
1137
|
direction: z.enum(["from", "to", "both"]).optional().default("both").describe("Which side of the edge the entity is on. 'from' = entity is the source (X --[?]--> ?). 'to' = entity is the target (? --[?]--> X). 'both' = either (default)."),
|
|
948
1138
|
workspace_id: z.string().optional().describe("[Removed in v0.4.0] No-op."),
|
|
949
|
-
destination: z.string().optional().describe("Optional destination override.
|
|
1139
|
+
destination: z.string().optional().describe("Optional legacy single-destination override. Do not combine with search_scope."),
|
|
1140
|
+
search_scope: searchScopeSchema.describe("Optional read/search scope. Accepts 'routed', 'all', a destination name, a configured scope name, a comma-separated destination list, or an array of destination names. Omit to use config.default_search_scope."),
|
|
950
1141
|
include_legacy_workspace: z.boolean().optional().describe("[Removed in v0.4.0] No-op."),
|
|
951
|
-
}, async ({ entity, relation_type, direction, workspace_id: _workspace_id, destination, include_legacy_workspace: _include_legacy_workspace }) => {
|
|
1142
|
+
}, async ({ entity, relation_type, direction, workspace_id: _workspace_id, destination, search_scope, include_legacy_workspace: _include_legacy_workspace }) => {
|
|
952
1143
|
const guard = requireReady();
|
|
953
1144
|
if (guard)
|
|
954
1145
|
return guard;
|
|
955
1146
|
const entityName = entity.toLowerCase();
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1147
|
+
const scopeResolved = resolveSearchScopeOrError({
|
|
1148
|
+
destination,
|
|
1149
|
+
search_scope,
|
|
1150
|
+
input: routingInput({ entities: [entityName] }),
|
|
1151
|
+
});
|
|
1152
|
+
if (scopeResolved.error)
|
|
1153
|
+
return scopeResolved.error;
|
|
1154
|
+
const searchScope = scopeResolved.scope;
|
|
960
1155
|
const results = [];
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1156
|
+
const failures = [];
|
|
1157
|
+
for (const dest of searchScope.destinations) {
|
|
1158
|
+
try {
|
|
1159
|
+
if (direction === "from" || direction === "both") {
|
|
1160
|
+
const filter = buildFilter({}) ?? { must: [] };
|
|
1161
|
+
filter.must.push({ key: "from_entity", match: { value: entityName } });
|
|
1162
|
+
if (relation_type) {
|
|
1163
|
+
filter.must.push({ key: "relation_type", match: { value: relation_type.toLowerCase() } });
|
|
1164
|
+
}
|
|
1165
|
+
const r = await qdrantScroll(dest, filter, 50);
|
|
1166
|
+
results.push(...(r.result?.points ?? []).map((point) => withDestination(point, dest)));
|
|
1167
|
+
}
|
|
1168
|
+
if (direction === "to" || direction === "both") {
|
|
1169
|
+
const filter = buildFilter({}) ?? { must: [] };
|
|
1170
|
+
filter.must.push({ key: "to_entity", match: { value: entityName } });
|
|
1171
|
+
if (relation_type) {
|
|
1172
|
+
filter.must.push({ key: "relation_type", match: { value: relation_type.toLowerCase() } });
|
|
1173
|
+
}
|
|
1174
|
+
const r = await qdrantScroll(dest, filter, 50);
|
|
1175
|
+
results.push(...(r.result?.points ?? []).map((point) => withDestination(point, dest)));
|
|
1176
|
+
}
|
|
966
1177
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
if (direction === "to" || direction === "both") {
|
|
971
|
-
const filter = buildFilter({}) ?? { must: [] };
|
|
972
|
-
filter.must.push({ key: "to_entity", match: { value: entityName } });
|
|
973
|
-
if (relation_type) {
|
|
974
|
-
filter.must.push({ key: "relation_type", match: { value: relation_type.toLowerCase() } });
|
|
1178
|
+
catch (e) {
|
|
1179
|
+
failures.push({ destination: dest.name, error: e instanceof Error ? e.message : String(e) });
|
|
975
1180
|
}
|
|
976
|
-
|
|
977
|
-
|
|
1181
|
+
}
|
|
1182
|
+
if (failures.length === searchScope.destinations.length && searchScope.destinations.length > 0) {
|
|
1183
|
+
return {
|
|
1184
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1185
|
+
status: "search_failed",
|
|
1186
|
+
entity: entityName,
|
|
1187
|
+
search_scope: searchScope.name,
|
|
1188
|
+
searched_destinations: searchScope.destinations.map((dest) => dest.name),
|
|
1189
|
+
failed_destinations: failures,
|
|
1190
|
+
}, null, 2) }],
|
|
1191
|
+
isError: true,
|
|
1192
|
+
};
|
|
978
1193
|
}
|
|
979
1194
|
const seen = new Set();
|
|
980
1195
|
const unique = results.filter((r) => {
|
|
981
|
-
|
|
1196
|
+
const key = `${r._destination.name}:${r.id}`;
|
|
1197
|
+
if (seen.has(key))
|
|
982
1198
|
return false;
|
|
983
|
-
seen.add(
|
|
1199
|
+
seen.add(key);
|
|
984
1200
|
return true;
|
|
985
1201
|
});
|
|
986
1202
|
if (unique.length === 0) {
|
|
987
|
-
|
|
1203
|
+
const warning = failures.length > 0
|
|
1204
|
+
? ` Search warnings: ${failures.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")}`
|
|
1205
|
+
: "";
|
|
1206
|
+
return { content: [{ type: "text", text: `No relations found for '${entity}'.${warning}` }] };
|
|
988
1207
|
}
|
|
1208
|
+
const includeDestination = searchScope.destinations.length > 1 || searchScope.name === "all";
|
|
989
1209
|
const lines = unique.map((r) => {
|
|
990
1210
|
const p = r.payload;
|
|
991
|
-
|
|
1211
|
+
const prefix = includeDestination ? `[${r._destination.name}] ` : "";
|
|
1212
|
+
return `${prefix}${p.from_entity} --[${p.relation_type}]--> ${p.to_entity} (confidence: ${p.confidence}, id: ${r.id})`;
|
|
992
1213
|
});
|
|
1214
|
+
if (failures.length > 0) {
|
|
1215
|
+
lines.push("", `Search warnings: ${failures.map((failure) => `${failure.destination}: ${failure.error}`).join("; ")}`);
|
|
1216
|
+
}
|
|
993
1217
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
994
1218
|
});
|
|
995
1219
|
// ── memory_forget ───────────────────────────────────────────────────────
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BikkyConfig, Destination, SearchScopeTarget } from "./config.js";
|
|
2
|
+
import { type RoutingInput } from "./routing.js";
|
|
3
|
+
export type SearchScopeInput = SearchScopeTarget | null | undefined;
|
|
4
|
+
export interface AvailableSearchScope {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
destinations: "routed" | "all" | string[];
|
|
8
|
+
default: boolean;
|
|
9
|
+
source: "builtin" | "destination" | "config";
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvedSearchScope {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
requested: SearchScopeTarget;
|
|
15
|
+
destinations: Destination[];
|
|
16
|
+
}
|
|
17
|
+
export declare class SearchScopeNotFoundError extends Error {
|
|
18
|
+
readonly scope: string;
|
|
19
|
+
readonly available: string[];
|
|
20
|
+
constructor(scope: string, available: string[]);
|
|
21
|
+
}
|
|
22
|
+
export declare function availableSearchScopes(config: BikkyConfig, destinations: ReadonlyArray<Destination>): AvailableSearchScope[];
|
|
23
|
+
export declare function resolveSearchScope(input: SearchScopeInput, config: BikkyConfig, destinations: ReadonlyArray<Destination>, routing: RoutingInput): ResolvedSearchScope;
|
|
24
|
+
//# sourceMappingURL=search-scope.d.ts.map
|