@usewhisper/mcp-server 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +908 -482
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { execSync, spawnSync } from "child_process";
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, appendFileSync } from "fs";
|
|
9
|
-
import { join, relative, extname } from "path";
|
|
9
|
+
import { join, relative, extname, normalize as normalizePath, resolve as resolvePath } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import { createHash, randomUUID } from "crypto";
|
|
12
12
|
|
|
@@ -2089,9 +2089,33 @@ ${lines.join("\n")}`;
|
|
|
2089
2089
|
}
|
|
2090
2090
|
});
|
|
2091
2091
|
}
|
|
2092
|
+
async resolveLearningScope(overrides) {
|
|
2093
|
+
const merged = {
|
|
2094
|
+
...this.baseContext,
|
|
2095
|
+
...overrides
|
|
2096
|
+
};
|
|
2097
|
+
const { scope } = await this.resolveScope(overrides);
|
|
2098
|
+
return {
|
|
2099
|
+
project: scope.project,
|
|
2100
|
+
sessionId: merged.sessionId || scope.sessionId,
|
|
2101
|
+
userId: merged.userId
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2092
2104
|
async afterTurn(input, context = {}) {
|
|
2093
2105
|
this.pushTouchedFiles(input.touchedFiles);
|
|
2094
|
-
|
|
2106
|
+
if (input.auto_learn === false) {
|
|
2107
|
+
return {
|
|
2108
|
+
success: true,
|
|
2109
|
+
sessionIngested: false,
|
|
2110
|
+
memoriesCreated: 0,
|
|
2111
|
+
relationsCreated: 0,
|
|
2112
|
+
invalidatedCount: 0,
|
|
2113
|
+
mergedCount: 0,
|
|
2114
|
+
droppedCount: 0,
|
|
2115
|
+
warnings: []
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
const scope = await this.resolveLearningScope(context);
|
|
2095
2119
|
const result = await this.args.adapter.ingestSession({
|
|
2096
2120
|
project: scope.project,
|
|
2097
2121
|
session_id: scope.sessionId,
|
|
@@ -2474,6 +2498,19 @@ var WhisperClient = class _WhisperClient {
|
|
|
2474
2498
|
});
|
|
2475
2499
|
return response.data;
|
|
2476
2500
|
}
|
|
2501
|
+
async learn(params) {
|
|
2502
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2503
|
+
const response = await this.runtimeClient.request({
|
|
2504
|
+
endpoint: "/v1/learn",
|
|
2505
|
+
method: "POST",
|
|
2506
|
+
operation: params.mode === "conversation" ? "session" : "bulk",
|
|
2507
|
+
body: {
|
|
2508
|
+
...params,
|
|
2509
|
+
project
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
return response.data;
|
|
2513
|
+
}
|
|
2477
2514
|
createAgentRuntime(options = {}) {
|
|
2478
2515
|
const baseContext = {
|
|
2479
2516
|
workspacePath: options.workspacePath,
|
|
@@ -2522,6 +2559,14 @@ var WhisperClient = class _WhisperClient {
|
|
|
2522
2559
|
sessionId: params.sessionId || context.sessionId || ""
|
|
2523
2560
|
})
|
|
2524
2561
|
},
|
|
2562
|
+
learn: (params) => base.learn({
|
|
2563
|
+
...params,
|
|
2564
|
+
project: params.project || context.project || base.config.project,
|
|
2565
|
+
...params.mode === "conversation" ? {
|
|
2566
|
+
user_id: params.user_id ?? context.userId,
|
|
2567
|
+
session_id: params.session_id || context.sessionId || ""
|
|
2568
|
+
} : {}
|
|
2569
|
+
}),
|
|
2525
2570
|
queue: base.queue,
|
|
2526
2571
|
diagnostics: base.diagnostics
|
|
2527
2572
|
};
|
|
@@ -2983,91 +3028,125 @@ var WhisperContext = class _WhisperContext {
|
|
|
2983
3028
|
return this.request(`/v1/sources/${sourceId}/sync`, { method: "POST" });
|
|
2984
3029
|
}
|
|
2985
3030
|
async addSourceByType(projectId, params) {
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3031
|
+
const result = await this.learn({
|
|
3032
|
+
mode: "source",
|
|
3033
|
+
project: projectId,
|
|
3034
|
+
type: "video",
|
|
3035
|
+
name: params.name,
|
|
3036
|
+
url: params.url,
|
|
3037
|
+
platform: params.platform,
|
|
3038
|
+
language: params.language,
|
|
3039
|
+
options: {
|
|
3040
|
+
async: true,
|
|
3041
|
+
auto_index: params.auto_sync ?? true,
|
|
3042
|
+
ingestion_profile: params.ingestion_profile,
|
|
3043
|
+
strategy_override: params.strategy_override,
|
|
3044
|
+
profile_config: params.profile_config,
|
|
3045
|
+
allow_stt_fallback: params.allow_stt_fallback,
|
|
3046
|
+
max_duration_minutes: params.max_duration_minutes
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
return {
|
|
3050
|
+
source_id: result.source_id,
|
|
3051
|
+
sync_job_id: result.job_id ?? null,
|
|
3052
|
+
status: result.status === "processing" || result.status === "queued" ? result.status : "created"
|
|
3053
|
+
};
|
|
2993
3054
|
}
|
|
2994
3055
|
async getSourceStatus(sourceId) {
|
|
2995
3056
|
return this.request(`/v1/sources/${sourceId}/status`, { method: "GET" });
|
|
2996
3057
|
}
|
|
2997
3058
|
async createCanonicalSource(project, params) {
|
|
2998
|
-
const
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
connector_type,
|
|
3036
|
-
config
|
|
3059
|
+
const result = await this.learn({
|
|
3060
|
+
mode: "source",
|
|
3061
|
+
project,
|
|
3062
|
+
type: params.type,
|
|
3063
|
+
name: params.name,
|
|
3064
|
+
metadata: params.metadata,
|
|
3065
|
+
owner: params.owner,
|
|
3066
|
+
repo: params.repo,
|
|
3067
|
+
branch: params.branch,
|
|
3068
|
+
paths: params.paths,
|
|
3069
|
+
url: params.url,
|
|
3070
|
+
file_path: params.file_path,
|
|
3071
|
+
path: params.path,
|
|
3072
|
+
channel_ids: params.channel_ids,
|
|
3073
|
+
since: params.since,
|
|
3074
|
+
token: params.token,
|
|
3075
|
+
auth_ref: params.auth_ref,
|
|
3076
|
+
platform: params.platform,
|
|
3077
|
+
language: params.language,
|
|
3078
|
+
options: {
|
|
3079
|
+
async: true,
|
|
3080
|
+
auto_index: params.auto_index ?? true,
|
|
3081
|
+
ingestion_profile: params.ingestion_profile,
|
|
3082
|
+
strategy_override: params.strategy_override,
|
|
3083
|
+
profile_config: params.profile_config,
|
|
3084
|
+
crawl_depth: params.crawl_depth,
|
|
3085
|
+
include_paths: params.include_paths,
|
|
3086
|
+
exclude_paths: params.exclude_paths,
|
|
3087
|
+
glob: params.glob,
|
|
3088
|
+
max_files: params.max_files,
|
|
3089
|
+
max_pages: params.max_pages,
|
|
3090
|
+
extract_mode: params.extract_mode,
|
|
3091
|
+
workspace_id: params.workspace_id,
|
|
3092
|
+
allow_stt_fallback: params.allow_stt_fallback,
|
|
3093
|
+
max_duration_minutes: params.max_duration_minutes,
|
|
3094
|
+
max_chunks: params.max_chunks
|
|
3095
|
+
}
|
|
3037
3096
|
});
|
|
3038
|
-
let status = "queued";
|
|
3039
|
-
let jobId = null;
|
|
3040
|
-
if (params.auto_index ?? true) {
|
|
3041
|
-
const syncRes = await this.syncSource(created.id);
|
|
3042
|
-
status = "indexing";
|
|
3043
|
-
jobId = String(syncRes?.id || syncRes?.job_id || "");
|
|
3044
|
-
}
|
|
3045
3097
|
return {
|
|
3046
|
-
source_id:
|
|
3047
|
-
status,
|
|
3048
|
-
job_id:
|
|
3049
|
-
index_started:
|
|
3098
|
+
source_id: result.source_id,
|
|
3099
|
+
status: result.status === "processing" ? "indexing" : result.status === "created" ? "queued" : result.status,
|
|
3100
|
+
job_id: result.job_id ?? null,
|
|
3101
|
+
index_started: result.index_started,
|
|
3050
3102
|
warnings: []
|
|
3051
3103
|
};
|
|
3052
3104
|
}
|
|
3053
3105
|
async ingest(projectId, documents) {
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3106
|
+
await Promise.all(
|
|
3107
|
+
documents.map(
|
|
3108
|
+
(doc) => this.learn({
|
|
3109
|
+
mode: "text",
|
|
3110
|
+
project: projectId,
|
|
3111
|
+
title: doc.title,
|
|
3112
|
+
content: doc.content,
|
|
3113
|
+
metadata: {
|
|
3114
|
+
...doc.metadata || {},
|
|
3115
|
+
...doc.file_path ? { file_path: doc.file_path } : {}
|
|
3116
|
+
},
|
|
3117
|
+
options: {
|
|
3118
|
+
async: true,
|
|
3119
|
+
ingestion_profile: doc.ingestion_profile,
|
|
3120
|
+
strategy_override: doc.strategy_override,
|
|
3121
|
+
profile_config: doc.profile_config
|
|
3122
|
+
}
|
|
3123
|
+
})
|
|
3124
|
+
)
|
|
3060
3125
|
);
|
|
3126
|
+
return { ingested: documents.length };
|
|
3061
3127
|
}
|
|
3062
3128
|
async addContext(params) {
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3129
|
+
await this.learn({
|
|
3130
|
+
mode: "text",
|
|
3131
|
+
project: this.getRequiredProject(params.project),
|
|
3132
|
+
title: params.title || "Context",
|
|
3133
|
+
content: params.content,
|
|
3134
|
+
metadata: params.metadata || { source: "addContext" },
|
|
3135
|
+
options: {
|
|
3136
|
+
async: true
|
|
3069
3137
|
}
|
|
3070
|
-
|
|
3138
|
+
});
|
|
3139
|
+
return { ingested: 1 };
|
|
3140
|
+
}
|
|
3141
|
+
async learn(params) {
|
|
3142
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
3143
|
+
return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/learn", {
|
|
3144
|
+
method: "POST",
|
|
3145
|
+
body: JSON.stringify({
|
|
3146
|
+
...params,
|
|
3147
|
+
project
|
|
3148
|
+
})
|
|
3149
|
+
}));
|
|
3071
3150
|
}
|
|
3072
3151
|
async listMemories(params) {
|
|
3073
3152
|
const projectRef = this.getRequiredProject(params.project);
|
|
@@ -3800,15 +3879,50 @@ function ensureStateDir() {
|
|
|
3800
3879
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
3801
3880
|
}
|
|
3802
3881
|
}
|
|
3882
|
+
function canonicalizeWorkspacePath(path, cwd = process.cwd()) {
|
|
3883
|
+
const basePath = path?.trim() ? path.trim() : cwd;
|
|
3884
|
+
const absolute = resolvePath(cwd, basePath);
|
|
3885
|
+
const normalized = normalizePath(absolute).replace(/\\/g, "/");
|
|
3886
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
3887
|
+
}
|
|
3888
|
+
function chooseWorkspaceProjectSource(args) {
|
|
3889
|
+
const explicitProject = args.explicitProject?.trim();
|
|
3890
|
+
if (explicitProject) {
|
|
3891
|
+
return { project_ref: explicitProject, resolved_by: "explicit_project" };
|
|
3892
|
+
}
|
|
3893
|
+
const workspaceProjectRef = args.workspaceProjectRef?.trim();
|
|
3894
|
+
if (workspaceProjectRef) {
|
|
3895
|
+
return { project_ref: workspaceProjectRef, resolved_by: "workspace_binding" };
|
|
3896
|
+
}
|
|
3897
|
+
const defaultProject = args.defaultProject?.trim();
|
|
3898
|
+
if (defaultProject) {
|
|
3899
|
+
return { project_ref: defaultProject, resolved_by: "env_default" };
|
|
3900
|
+
}
|
|
3901
|
+
return { project_ref: null, resolved_by: "unresolved" };
|
|
3902
|
+
}
|
|
3903
|
+
function classifyWorkspaceHealth(args) {
|
|
3904
|
+
if (!args.project_ref) return "unbound";
|
|
3905
|
+
if (!args.last_indexed_at || (args.coverage ?? 0) <= 0) return "unindexed";
|
|
3906
|
+
const nowMs = args.now_ms ?? Date.now();
|
|
3907
|
+
const lastIndexedMs = new Date(args.last_indexed_at).getTime();
|
|
3908
|
+
const ageHours = Number.isFinite(lastIndexedMs) ? (nowMs - lastIndexedMs) / (60 * 60 * 1e3) : Number.POSITIVE_INFINITY;
|
|
3909
|
+
const pendingChanges = args.pending_changes ?? 0;
|
|
3910
|
+
const drifted = pendingChanges > 0 || Boolean(
|
|
3911
|
+
args.last_indexed_commit && args.current_commit && args.last_indexed_commit !== args.current_commit
|
|
3912
|
+
);
|
|
3913
|
+
if (drifted) return "drifted";
|
|
3914
|
+
if (ageHours > (args.max_staleness_hours ?? 168)) return "stale";
|
|
3915
|
+
return "healthy";
|
|
3916
|
+
}
|
|
3803
3917
|
function getWorkspaceId(workspaceId) {
|
|
3804
3918
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3805
|
-
const seed = `${process.cwd()}|${
|
|
3919
|
+
const seed = `${canonicalizeWorkspacePath(process.cwd())}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3806
3920
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3807
3921
|
}
|
|
3808
3922
|
function getWorkspaceIdForPath(path, workspaceId) {
|
|
3809
3923
|
if (workspaceId?.trim()) return workspaceId.trim();
|
|
3810
3924
|
if (!path) return getWorkspaceId(void 0);
|
|
3811
|
-
const seed = `${path}|${
|
|
3925
|
+
const seed = `${canonicalizeWorkspacePath(path)}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3812
3926
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3813
3927
|
}
|
|
3814
3928
|
function clamp012(value) {
|
|
@@ -3899,7 +4013,10 @@ function getWorkspaceState(state, workspaceId) {
|
|
|
3899
4013
|
annotations: [],
|
|
3900
4014
|
session_summaries: [],
|
|
3901
4015
|
events: [],
|
|
3902
|
-
index_metadata: {}
|
|
4016
|
+
index_metadata: {},
|
|
4017
|
+
root_path: void 0,
|
|
4018
|
+
project_ref: void 0,
|
|
4019
|
+
project_id: void 0
|
|
3903
4020
|
};
|
|
3904
4021
|
}
|
|
3905
4022
|
return state.workspaces[workspaceId];
|
|
@@ -3909,17 +4026,124 @@ function computeChecksum(value) {
|
|
|
3909
4026
|
}
|
|
3910
4027
|
var cachedProjectRef = DEFAULT_PROJECT || void 0;
|
|
3911
4028
|
var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
|
|
4029
|
+
async function resolveProjectDescriptor(projectRef) {
|
|
4030
|
+
try {
|
|
4031
|
+
const resolved = await whisper.resolveProject(projectRef);
|
|
4032
|
+
const resolvedRef = resolved.slug || resolved.name || resolved.id || projectRef;
|
|
4033
|
+
cachedProjectRef = resolvedRef;
|
|
4034
|
+
return { project_ref: resolvedRef, project_id: resolved.id || null };
|
|
4035
|
+
} catch {
|
|
4036
|
+
cachedProjectRef = projectRef;
|
|
4037
|
+
return { project_ref: projectRef, project_id: null };
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
function getWorkspaceRecommendedNextCalls(health) {
|
|
4041
|
+
if (health === "unbound") return ["index.workspace_resolve", "context.list_projects", "index.workspace_status"];
|
|
4042
|
+
if (health === "unindexed") return ["index.workspace_status", "index.workspace_run", "search_code", "grep"];
|
|
4043
|
+
if (health === "stale" || health === "drifted") return ["index.workspace_status", "index.workspace_run", "grep", "search_code"];
|
|
4044
|
+
return [];
|
|
4045
|
+
}
|
|
4046
|
+
function getWorkspaceWarnings(args) {
|
|
4047
|
+
if (args.health === "unbound") {
|
|
4048
|
+
return [`Workspace ${args.rootPath} is not bound to a Whisper project.`];
|
|
4049
|
+
}
|
|
4050
|
+
if (args.health === "unindexed") {
|
|
4051
|
+
return [`Workspace ${args.rootPath} is bound to ${args.projectRef}, but no usable local index metadata exists yet.`];
|
|
4052
|
+
}
|
|
4053
|
+
if (args.health === "stale") {
|
|
4054
|
+
const age = args.freshness.age_hours == null ? "unknown" : `${Math.round(args.freshness.age_hours)}h`;
|
|
4055
|
+
return [`Workspace index for ${args.projectRef} is stale (${age} old).`];
|
|
4056
|
+
}
|
|
4057
|
+
if (args.health === "drifted") {
|
|
4058
|
+
const reasons = [];
|
|
4059
|
+
if (args.pendingChanges && args.pendingChanges > 0) reasons.push(`${args.pendingChanges} pending local changes`);
|
|
4060
|
+
if (args.lastIndexedCommit && args.currentCommit && args.lastIndexedCommit !== args.currentCommit) {
|
|
4061
|
+
reasons.push(`indexed commit ${args.lastIndexedCommit.slice(0, 12)} differs from current ${args.currentCommit.slice(0, 12)}`);
|
|
4062
|
+
}
|
|
4063
|
+
const suffix = reasons.length ? `: ${reasons.join("; ")}` : "";
|
|
4064
|
+
return [`Workspace state drifted since the last index${suffix}.`];
|
|
4065
|
+
}
|
|
4066
|
+
return [];
|
|
4067
|
+
}
|
|
4068
|
+
async function resolveWorkspaceTrust(args) {
|
|
4069
|
+
const rootPath = canonicalizeWorkspacePath(args.path);
|
|
4070
|
+
const workspaceId = getWorkspaceIdForPath(rootPath, args.workspace_id);
|
|
4071
|
+
const state = loadState();
|
|
4072
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
4073
|
+
let mutated = false;
|
|
4074
|
+
if (workspace.root_path !== rootPath) {
|
|
4075
|
+
workspace.root_path = rootPath;
|
|
4076
|
+
mutated = true;
|
|
4077
|
+
}
|
|
4078
|
+
const selected = chooseWorkspaceProjectSource({
|
|
4079
|
+
explicitProject: args.project,
|
|
4080
|
+
workspaceProjectRef: workspace.project_ref,
|
|
4081
|
+
defaultProject: DEFAULT_PROJECT || null
|
|
4082
|
+
});
|
|
4083
|
+
let projectRef = null;
|
|
4084
|
+
let projectId = workspace.project_id || null;
|
|
4085
|
+
if (selected.project_ref) {
|
|
4086
|
+
const resolved = await resolveProjectDescriptor(selected.project_ref);
|
|
4087
|
+
projectRef = resolved.project_ref;
|
|
4088
|
+
projectId = resolved.project_id;
|
|
4089
|
+
if (workspace.project_ref !== projectRef || (workspace.project_id || null) !== projectId) {
|
|
4090
|
+
workspace.project_ref = projectRef;
|
|
4091
|
+
workspace.project_id = projectId || void 0;
|
|
4092
|
+
mutated = true;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
if (mutated) saveState(state);
|
|
4096
|
+
const lastIndexedAt = workspace.index_metadata?.last_indexed_at || null;
|
|
4097
|
+
const ageHours = lastIndexedAt ? (Date.now() - new Date(lastIndexedAt).getTime()) / (60 * 60 * 1e3) : null;
|
|
4098
|
+
const currentCommit = getGitHead(rootPath) || null;
|
|
4099
|
+
const pendingChanges = getGitPendingCount(rootPath);
|
|
4100
|
+
const lastIndexedCommit = workspace.index_metadata?.last_indexed_commit || null;
|
|
4101
|
+
const coverage = workspace.index_metadata?.coverage ?? 0;
|
|
4102
|
+
const health = classifyWorkspaceHealth({
|
|
4103
|
+
project_ref: projectRef,
|
|
4104
|
+
coverage,
|
|
4105
|
+
last_indexed_at: lastIndexedAt,
|
|
4106
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4107
|
+
current_commit: currentCommit,
|
|
4108
|
+
pending_changes: pendingChanges ?? null,
|
|
4109
|
+
max_staleness_hours: args.max_staleness_hours ?? 168
|
|
4110
|
+
});
|
|
4111
|
+
const freshness = {
|
|
4112
|
+
stale: health === "stale",
|
|
4113
|
+
age_hours: ageHours,
|
|
4114
|
+
last_indexed_at: lastIndexedAt
|
|
4115
|
+
};
|
|
4116
|
+
const warnings = getWorkspaceWarnings({
|
|
4117
|
+
health,
|
|
4118
|
+
rootPath,
|
|
4119
|
+
projectRef,
|
|
4120
|
+
currentCommit,
|
|
4121
|
+
lastIndexedCommit,
|
|
4122
|
+
pendingChanges: pendingChanges ?? null,
|
|
4123
|
+
freshness
|
|
4124
|
+
});
|
|
4125
|
+
return {
|
|
4126
|
+
workspace_id: workspaceId,
|
|
4127
|
+
root_path: rootPath,
|
|
4128
|
+
project_ref: projectRef,
|
|
4129
|
+
project_id: projectId,
|
|
4130
|
+
resolved_by: selected.resolved_by,
|
|
4131
|
+
health,
|
|
4132
|
+
warnings,
|
|
4133
|
+
recommended_next_calls: getWorkspaceRecommendedNextCalls(health),
|
|
4134
|
+
freshness,
|
|
4135
|
+
coverage,
|
|
4136
|
+
last_indexed_commit: lastIndexedCommit,
|
|
4137
|
+
current_commit: currentCommit,
|
|
4138
|
+
pending_changes: pendingChanges ?? null,
|
|
4139
|
+
grounded_to_workspace: health === "healthy"
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
3912
4142
|
async function resolveProjectRef(explicit) {
|
|
3913
4143
|
if (explicit?.trim()) {
|
|
3914
4144
|
const requestedRef = explicit.trim();
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
cachedProjectRef = resolved.slug || resolved.name || resolved.id;
|
|
3918
|
-
return cachedProjectRef;
|
|
3919
|
-
} catch {
|
|
3920
|
-
cachedProjectRef = requestedRef;
|
|
3921
|
-
return requestedRef;
|
|
3922
|
-
}
|
|
4145
|
+
const resolved = await resolveProjectDescriptor(requestedRef);
|
|
4146
|
+
return resolved.project_ref;
|
|
3923
4147
|
}
|
|
3924
4148
|
if (cachedProjectRef) return cachedProjectRef;
|
|
3925
4149
|
try {
|
|
@@ -4022,7 +4246,9 @@ function buildAbstain(args) {
|
|
|
4022
4246
|
reason: args.reason,
|
|
4023
4247
|
message: args.message,
|
|
4024
4248
|
closest_evidence: args.closest_evidence,
|
|
4025
|
-
|
|
4249
|
+
warnings: args.warnings || [],
|
|
4250
|
+
trust_state: args.trust_state,
|
|
4251
|
+
recommended_next_calls: args.recommended_next_calls || ["index.workspace_status", "index.workspace_run", "grep", "context.get_relevant"],
|
|
4026
4252
|
diagnostics: {
|
|
4027
4253
|
claims_evaluated: args.claims_evaluated,
|
|
4028
4254
|
evidence_items_found: args.evidence_items_found,
|
|
@@ -4098,6 +4324,86 @@ function formatCanonicalMemoryResults(rawResults) {
|
|
|
4098
4324
|
};
|
|
4099
4325
|
});
|
|
4100
4326
|
}
|
|
4327
|
+
function normalizeLooseText(value) {
|
|
4328
|
+
return String(value || "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
4329
|
+
}
|
|
4330
|
+
function resolveForgetQueryCandidates(rawResults, query) {
|
|
4331
|
+
const normalizedQuery = normalizeLooseText(query);
|
|
4332
|
+
const candidates = normalizeCanonicalResults(rawResults).map((result) => {
|
|
4333
|
+
const memory = result?.memory || result;
|
|
4334
|
+
const metadata = memory?.metadata || {};
|
|
4335
|
+
return {
|
|
4336
|
+
id: String(memory?.id || "").trim(),
|
|
4337
|
+
content: normalizeLooseText(memory?.content),
|
|
4338
|
+
normalized_content: normalizeLooseText(metadata?.normalized_content),
|
|
4339
|
+
canonical_content: normalizeLooseText(metadata?.canonical_content),
|
|
4340
|
+
similarity: typeof result?.similarity === "number" ? result.similarity : typeof result?.score === "number" ? result.score : 0
|
|
4341
|
+
};
|
|
4342
|
+
}).filter((candidate) => candidate.id);
|
|
4343
|
+
const exactMatches = candidates.filter(
|
|
4344
|
+
(candidate) => candidate.id.toLowerCase() === normalizedQuery || candidate.content === normalizedQuery || candidate.normalized_content === normalizedQuery || candidate.canonical_content === normalizedQuery
|
|
4345
|
+
);
|
|
4346
|
+
if (exactMatches.length > 0) {
|
|
4347
|
+
return { memory_ids: [...new Set(exactMatches.map((candidate) => candidate.id))], resolved_by: "exact" };
|
|
4348
|
+
}
|
|
4349
|
+
const substringMatches = candidates.filter((candidate) => {
|
|
4350
|
+
const haystacks = [candidate.content, candidate.normalized_content, candidate.canonical_content].filter(Boolean);
|
|
4351
|
+
return haystacks.some(
|
|
4352
|
+
(value) => value.includes(normalizedQuery) || normalizedQuery.length >= 12 && normalizedQuery.includes(value)
|
|
4353
|
+
);
|
|
4354
|
+
});
|
|
4355
|
+
if (substringMatches.length === 1) {
|
|
4356
|
+
return { memory_ids: [substringMatches[0].id], resolved_by: "substring" };
|
|
4357
|
+
}
|
|
4358
|
+
if (substringMatches.length > 1) {
|
|
4359
|
+
return {
|
|
4360
|
+
memory_ids: [],
|
|
4361
|
+
resolved_by: "ambiguous",
|
|
4362
|
+
warning: "Query matched multiple memories by recognizable text. Use memory_id or a more specific query."
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4365
|
+
const ranked = [...candidates].sort((a, b) => b.similarity - a.similarity);
|
|
4366
|
+
if (ranked[0] && ranked[0].similarity >= 0.9 && (!ranked[1] || ranked[0].similarity - ranked[1].similarity >= 0.08)) {
|
|
4367
|
+
return { memory_ids: [ranked[0].id], resolved_by: "high_confidence" };
|
|
4368
|
+
}
|
|
4369
|
+
return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
|
|
4370
|
+
}
|
|
4371
|
+
async function searchMemoriesForContextQuery(args) {
|
|
4372
|
+
return whisper.searchMemoriesSOTA({
|
|
4373
|
+
project: args.project,
|
|
4374
|
+
query: args.query,
|
|
4375
|
+
user_id: args.user_id,
|
|
4376
|
+
session_id: args.session_id,
|
|
4377
|
+
top_k: args.top_k ?? 5,
|
|
4378
|
+
include_relations: false,
|
|
4379
|
+
include_pending: true
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4382
|
+
async function runContextQueryMemoryRescue(args) {
|
|
4383
|
+
const scoped = await searchMemoriesForContextQuery(args);
|
|
4384
|
+
const scopedResults = formatCanonicalMemoryResults(scoped);
|
|
4385
|
+
if (scopedResults.length > 0) {
|
|
4386
|
+
return { results: scopedResults, rescue_mode: "scoped" };
|
|
4387
|
+
}
|
|
4388
|
+
if (args.user_id || args.session_id) {
|
|
4389
|
+
return { results: [], rescue_mode: null };
|
|
4390
|
+
}
|
|
4391
|
+
const broad = await searchMemoriesForContextQuery({
|
|
4392
|
+
project: args.project,
|
|
4393
|
+
query: args.query,
|
|
4394
|
+
top_k: args.top_k
|
|
4395
|
+
});
|
|
4396
|
+
return { results: formatCanonicalMemoryResults(broad), rescue_mode: formatCanonicalMemoryResults(broad).length > 0 ? "project_broad" : null };
|
|
4397
|
+
}
|
|
4398
|
+
function renderContextQueryMemoryRescue(args) {
|
|
4399
|
+
const lines = args.results.map(
|
|
4400
|
+
(result, index) => `${index + 1}. [${result.memory_type || "memory"}, score: ${result.similarity ?? "n/a"}] ${result.content}`
|
|
4401
|
+
);
|
|
4402
|
+
const scopeLabel = args.rescue_mode === "project_broad" ? `project=${args.project}, project_broad_memory_rescue=true` : `project=${args.project}, user=${args.scope.userId}, session=${args.scope.sessionId}, scoped_memory_rescue=true`;
|
|
4403
|
+
return `Found ${args.results.length} memory result(s) (${scopeLabel}):
|
|
4404
|
+
|
|
4405
|
+
${lines.join("\n\n")}`;
|
|
4406
|
+
}
|
|
4101
4407
|
function likelyEmbeddingFailure(error) {
|
|
4102
4408
|
const message = String(error?.message || error || "").toLowerCase();
|
|
4103
4409
|
return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
|
|
@@ -4139,6 +4445,186 @@ async function queryWithDegradedFallback(params) {
|
|
|
4139
4445
|
};
|
|
4140
4446
|
}
|
|
4141
4447
|
}
|
|
4448
|
+
function collectCodeFiles(rootPath, allowedExts, maxFiles) {
|
|
4449
|
+
const files = [];
|
|
4450
|
+
function collect(dir) {
|
|
4451
|
+
if (files.length >= maxFiles) return;
|
|
4452
|
+
let entries;
|
|
4453
|
+
try {
|
|
4454
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
4455
|
+
} catch {
|
|
4456
|
+
return;
|
|
4457
|
+
}
|
|
4458
|
+
for (const entry of entries) {
|
|
4459
|
+
if (files.length >= maxFiles) break;
|
|
4460
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
4461
|
+
const full = join(dir, entry.name);
|
|
4462
|
+
if (entry.isDirectory()) collect(full);
|
|
4463
|
+
else if (entry.isFile()) {
|
|
4464
|
+
const ext = extname(entry.name).replace(".", "");
|
|
4465
|
+
if (allowedExts.has(ext)) files.push(full);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
collect(rootPath);
|
|
4470
|
+
return files;
|
|
4471
|
+
}
|
|
4472
|
+
function tokenizeQueryForLexicalRescue(query) {
|
|
4473
|
+
const stopWords = /* @__PURE__ */ new Set(["where", "what", "show", "find", "logic", "code", "file", "files", "handled", "handling"]);
|
|
4474
|
+
return Array.from(new Set(
|
|
4475
|
+
query.toLowerCase().split(/[^a-z0-9/_-]+/).map((token) => token.trim()).filter((token) => token.length >= 3 && !stopWords.has(token))
|
|
4476
|
+
));
|
|
4477
|
+
}
|
|
4478
|
+
function buildLexicalRescueResults(args) {
|
|
4479
|
+
const tokens = tokenizeQueryForLexicalRescue(args.query);
|
|
4480
|
+
if (tokens.length === 0) return [];
|
|
4481
|
+
const scored = args.documents.map((doc) => {
|
|
4482
|
+
const haystack = `${doc.id}
|
|
4483
|
+
${doc.content}
|
|
4484
|
+
${doc.raw_content}`.toLowerCase();
|
|
4485
|
+
const tokenHits = tokens.filter((token) => haystack.includes(token));
|
|
4486
|
+
const uniqueHits = tokenHits.length;
|
|
4487
|
+
const exactPhraseBonus = haystack.includes(args.query.toLowerCase()) ? 0.25 : 0;
|
|
4488
|
+
const score = uniqueHits === 0 ? 0 : Math.min(0.95, uniqueHits / tokens.length + exactPhraseBonus);
|
|
4489
|
+
return {
|
|
4490
|
+
id: doc.id,
|
|
4491
|
+
score,
|
|
4492
|
+
content: doc.content,
|
|
4493
|
+
snippet: doc.content.split("\n").find((line) => line.trim().length > 10)?.slice(0, 200) || doc.id,
|
|
4494
|
+
search_mode: "lexical_rescue"
|
|
4495
|
+
};
|
|
4496
|
+
});
|
|
4497
|
+
return scored.filter((result) => result.score > 0).sort((a, b) => b.score - a.score).slice(0, args.top_k);
|
|
4498
|
+
}
|
|
4499
|
+
async function runSearchCodeSearch(args) {
|
|
4500
|
+
const topK = args.top_k ?? 10;
|
|
4501
|
+
const requestedThreshold = args.threshold ?? 0.2;
|
|
4502
|
+
const semanticDocuments = args.documents.map((doc) => ({ id: doc.id, content: doc.content }));
|
|
4503
|
+
const defaultResponse = await args.semantic_search({
|
|
4504
|
+
query: args.query,
|
|
4505
|
+
documents: semanticDocuments,
|
|
4506
|
+
top_k: topK,
|
|
4507
|
+
threshold: requestedThreshold
|
|
4508
|
+
});
|
|
4509
|
+
if (defaultResponse.results?.length) {
|
|
4510
|
+
return {
|
|
4511
|
+
results: defaultResponse.results.map((result) => ({ ...result, search_mode: "semantic" })),
|
|
4512
|
+
mode: "semantic",
|
|
4513
|
+
threshold_used: requestedThreshold,
|
|
4514
|
+
fallback_used: false,
|
|
4515
|
+
fallback_reason: null
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
const adaptiveThreshold = Math.max(0.05, Math.min(requestedThreshold, requestedThreshold / 2));
|
|
4519
|
+
if (adaptiveThreshold < requestedThreshold) {
|
|
4520
|
+
const adaptiveResponse = await args.semantic_search({
|
|
4521
|
+
query: args.query,
|
|
4522
|
+
documents: semanticDocuments,
|
|
4523
|
+
top_k: topK,
|
|
4524
|
+
threshold: adaptiveThreshold
|
|
4525
|
+
});
|
|
4526
|
+
if (adaptiveResponse.results?.length) {
|
|
4527
|
+
return {
|
|
4528
|
+
results: adaptiveResponse.results.map((result) => ({ ...result, search_mode: "adaptive_semantic" })),
|
|
4529
|
+
mode: "adaptive_semantic",
|
|
4530
|
+
threshold_used: adaptiveThreshold,
|
|
4531
|
+
fallback_used: true,
|
|
4532
|
+
fallback_reason: `No results at threshold ${requestedThreshold}; lowered to ${adaptiveThreshold}.`
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
const lexicalResults = buildLexicalRescueResults({
|
|
4537
|
+
query: args.query,
|
|
4538
|
+
documents: args.documents,
|
|
4539
|
+
top_k: topK
|
|
4540
|
+
});
|
|
4541
|
+
return {
|
|
4542
|
+
results: lexicalResults,
|
|
4543
|
+
mode: "lexical_rescue",
|
|
4544
|
+
threshold_used: null,
|
|
4545
|
+
fallback_used: true,
|
|
4546
|
+
fallback_reason: lexicalResults.length ? "Semantic ranking returned no matches; lexical rescue over local file paths and content was used." : "Semantic ranking returned no matches and lexical rescue found no strong candidates."
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
async function runSearchCodeTool(args) {
|
|
4550
|
+
const rootPath = canonicalizeWorkspacePath(args.path);
|
|
4551
|
+
const workspace = await resolveWorkspaceTrust({ path: rootPath });
|
|
4552
|
+
const allowedExts = args.file_types ? new Set(args.file_types) : CODE_EXTENSIONS;
|
|
4553
|
+
const files = collectCodeFiles(rootPath, allowedExts, args.max_files ?? 150);
|
|
4554
|
+
const sharedWarnings = [...workspace.warnings];
|
|
4555
|
+
if (files.length === 0) {
|
|
4556
|
+
return {
|
|
4557
|
+
tool: "search_code",
|
|
4558
|
+
query: args.query,
|
|
4559
|
+
path: rootPath,
|
|
4560
|
+
results: [],
|
|
4561
|
+
count: 0,
|
|
4562
|
+
warnings: sharedWarnings,
|
|
4563
|
+
diagnostics: {
|
|
4564
|
+
workspace_id: workspace.workspace_id,
|
|
4565
|
+
root_path: workspace.root_path,
|
|
4566
|
+
project_ref: workspace.project_ref,
|
|
4567
|
+
project_id: workspace.project_id,
|
|
4568
|
+
index_health: workspace.health,
|
|
4569
|
+
grounded_to_workspace: workspace.grounded_to_workspace,
|
|
4570
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4571
|
+
threshold_used: null,
|
|
4572
|
+
fallback_used: false,
|
|
4573
|
+
fallback_reason: null,
|
|
4574
|
+
search_mode: "semantic",
|
|
4575
|
+
warnings: sharedWarnings
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4579
|
+
const documents = [];
|
|
4580
|
+
for (const filePath of files) {
|
|
4581
|
+
try {
|
|
4582
|
+
const stat = statSync(filePath);
|
|
4583
|
+
if (stat.size > 500 * 1024) continue;
|
|
4584
|
+
const content = readFileSync(filePath, "utf-8");
|
|
4585
|
+
const relPath = relative(rootPath, filePath).replace(/\\/g, "/");
|
|
4586
|
+
documents.push({ id: relPath, content: extractSignature(relPath, content), raw_content: content });
|
|
4587
|
+
} catch {
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
const searchResult = await runSearchCodeSearch({
|
|
4591
|
+
query: args.query,
|
|
4592
|
+
documents,
|
|
4593
|
+
top_k: args.top_k,
|
|
4594
|
+
threshold: args.threshold,
|
|
4595
|
+
semantic_search: (params) => whisper.semanticSearch(params)
|
|
4596
|
+
});
|
|
4597
|
+
if (searchResult.mode === "lexical_rescue") {
|
|
4598
|
+
sharedWarnings.push("Local lexical rescue was used because semantic search returned no matches.");
|
|
4599
|
+
} else if (searchResult.mode === "adaptive_semantic") {
|
|
4600
|
+
sharedWarnings.push(searchResult.fallback_reason || "Adaptive semantic thresholding was used.");
|
|
4601
|
+
}
|
|
4602
|
+
if (!workspace.grounded_to_workspace && workspace.health !== "unbound" && workspace.health !== "unindexed") {
|
|
4603
|
+
sharedWarnings.push("Local code search is live, but project-backed retrieval may disagree until the workspace is re-indexed.");
|
|
4604
|
+
}
|
|
4605
|
+
return {
|
|
4606
|
+
tool: "search_code",
|
|
4607
|
+
query: args.query,
|
|
4608
|
+
path: rootPath,
|
|
4609
|
+
results: searchResult.results,
|
|
4610
|
+
count: searchResult.results.length,
|
|
4611
|
+
warnings: sharedWarnings,
|
|
4612
|
+
diagnostics: {
|
|
4613
|
+
workspace_id: workspace.workspace_id,
|
|
4614
|
+
root_path: workspace.root_path,
|
|
4615
|
+
project_ref: workspace.project_ref,
|
|
4616
|
+
project_id: workspace.project_id,
|
|
4617
|
+
index_health: workspace.health,
|
|
4618
|
+
grounded_to_workspace: workspace.grounded_to_workspace,
|
|
4619
|
+
threshold_requested: args.threshold ?? 0.2,
|
|
4620
|
+
threshold_used: searchResult.threshold_used,
|
|
4621
|
+
fallback_used: searchResult.fallback_used,
|
|
4622
|
+
fallback_reason: searchResult.fallback_reason,
|
|
4623
|
+
search_mode: searchResult.mode,
|
|
4624
|
+
warnings: sharedWarnings
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4142
4628
|
function getLocalAllowlistRoots() {
|
|
4143
4629
|
const fromEnv = (process.env.WHISPER_LOCAL_ALLOWLIST || "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
4144
4630
|
if (fromEnv.length > 0) return fromEnv;
|
|
@@ -4218,7 +4704,8 @@ async function ingestLocalPath(params) {
|
|
|
4218
4704
|
}
|
|
4219
4705
|
collect(rootPath);
|
|
4220
4706
|
const manifest = loadIngestManifest();
|
|
4221
|
-
const
|
|
4707
|
+
const canonicalRootPath = canonicalizeWorkspacePath(rootPath);
|
|
4708
|
+
const workspaceId = getWorkspaceIdForPath(canonicalRootPath);
|
|
4222
4709
|
if (!manifest[workspaceId]) manifest[workspaceId] = { last_run_at: (/* @__PURE__ */ new Date(0)).toISOString(), files: {} };
|
|
4223
4710
|
const docs = [];
|
|
4224
4711
|
const skipped = [];
|
|
@@ -4254,6 +4741,10 @@ async function ingestLocalPath(params) {
|
|
|
4254
4741
|
}
|
|
4255
4742
|
manifest[workspaceId].last_run_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4256
4743
|
saveIngestManifest(manifest);
|
|
4744
|
+
const state = loadState();
|
|
4745
|
+
const workspace = getWorkspaceState(state, workspaceId);
|
|
4746
|
+
workspace.root_path = canonicalRootPath;
|
|
4747
|
+
saveState(state);
|
|
4257
4748
|
appendFileSync(
|
|
4258
4749
|
AUDIT_LOG_PATH,
|
|
4259
4750
|
`${(/* @__PURE__ */ new Date()).toISOString()} local_ingest workspace=${workspaceId} root_hash=${createHash("sha256").update(rootPath).digest("hex").slice(0, 16)} files=${docs.length}
|
|
@@ -4262,79 +4753,40 @@ async function ingestLocalPath(params) {
|
|
|
4262
4753
|
return { ingested, scanned: files.length, queued: docs.length, skipped, workspace_id: workspaceId };
|
|
4263
4754
|
}
|
|
4264
4755
|
async function createSourceByType(params) {
|
|
4265
|
-
const
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
details: ingest
|
|
4298
|
-
};
|
|
4299
|
-
} else if (params.type === "slack") {
|
|
4300
|
-
config.channel_ids = params.channel_ids || [];
|
|
4301
|
-
if (params.since) config.since = params.since;
|
|
4302
|
-
if (params.workspace_id) config.workspace_id = params.workspace_id;
|
|
4303
|
-
if (params.token) config.token = params.token;
|
|
4304
|
-
if (params.auth_ref) config.auth_ref = params.auth_ref;
|
|
4305
|
-
} else if (params.type === "video") {
|
|
4306
|
-
if (!params.url) throw new Error("video source requires url");
|
|
4307
|
-
config.url = params.url;
|
|
4308
|
-
if (params.platform) config.platform = params.platform;
|
|
4309
|
-
if (params.language) config.language = params.language;
|
|
4310
|
-
if (params.allow_stt_fallback !== void 0) config.allow_stt_fallback = params.allow_stt_fallback;
|
|
4311
|
-
if (params.max_duration_minutes !== void 0) config.max_duration_minutes = params.max_duration_minutes;
|
|
4312
|
-
if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
|
|
4313
|
-
}
|
|
4314
|
-
if (params.metadata) config.metadata = params.metadata;
|
|
4315
|
-
if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
|
|
4316
|
-
if (params.strategy_override) config.strategy_override = params.strategy_override;
|
|
4317
|
-
if (params.profile_config) config.profile_config = params.profile_config;
|
|
4318
|
-
config.auto_index = params.auto_index ?? true;
|
|
4319
|
-
const created = await whisper.addSource(params.project, {
|
|
4320
|
-
name: params.name || `${params.type}-source-${Date.now()}`,
|
|
4321
|
-
connector_type,
|
|
4322
|
-
config
|
|
4756
|
+
const result = await whisper.createCanonicalSource(params.project, {
|
|
4757
|
+
type: params.type,
|
|
4758
|
+
name: params.name,
|
|
4759
|
+
auto_index: params.auto_index,
|
|
4760
|
+
metadata: params.metadata,
|
|
4761
|
+
ingestion_profile: params.ingestion_profile,
|
|
4762
|
+
strategy_override: params.strategy_override,
|
|
4763
|
+
profile_config: params.profile_config,
|
|
4764
|
+
owner: params.owner,
|
|
4765
|
+
repo: params.repo,
|
|
4766
|
+
branch: params.branch,
|
|
4767
|
+
paths: params.paths,
|
|
4768
|
+
url: params.url,
|
|
4769
|
+
crawl_depth: params.crawl_depth,
|
|
4770
|
+
include_paths: params.include_paths,
|
|
4771
|
+
exclude_paths: params.exclude_paths,
|
|
4772
|
+
file_path: params.file_path,
|
|
4773
|
+
path: params.path,
|
|
4774
|
+
glob: params.glob,
|
|
4775
|
+
max_files: params.max_files,
|
|
4776
|
+
max_pages: params.max_pages,
|
|
4777
|
+
extract_mode: params.extract_mode,
|
|
4778
|
+
workspace_id: params.workspace_id,
|
|
4779
|
+
channel_ids: params.channel_ids,
|
|
4780
|
+
since: params.since,
|
|
4781
|
+
token: params.token,
|
|
4782
|
+
auth_ref: params.auth_ref,
|
|
4783
|
+
platform: params.platform,
|
|
4784
|
+
language: params.language,
|
|
4785
|
+
allow_stt_fallback: params.allow_stt_fallback,
|
|
4786
|
+
max_duration_minutes: params.max_duration_minutes,
|
|
4787
|
+
max_chunks: params.max_chunks
|
|
4323
4788
|
});
|
|
4324
|
-
|
|
4325
|
-
let status = "queued";
|
|
4326
|
-
if (params.auto_index ?? true) {
|
|
4327
|
-
const syncRes = await whisper.syncSource(created.id);
|
|
4328
|
-
jobId = String(syncRes?.id || syncRes?.job_id || "");
|
|
4329
|
-
status = "indexing";
|
|
4330
|
-
}
|
|
4331
|
-
return {
|
|
4332
|
-
source_id: created.id,
|
|
4333
|
-
status,
|
|
4334
|
-
job_id: jobId || null,
|
|
4335
|
-
index_started: params.auto_index ?? true,
|
|
4336
|
-
warnings: []
|
|
4337
|
-
};
|
|
4789
|
+
return result;
|
|
4338
4790
|
}
|
|
4339
4791
|
function normalizeRecordMessages(input) {
|
|
4340
4792
|
if (Array.isArray(input.messages) && input.messages.length > 0) {
|
|
@@ -4358,85 +4810,60 @@ async function learnFromInput(input) {
|
|
|
4358
4810
|
if (!resolvedProject) {
|
|
4359
4811
|
throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
|
|
4360
4812
|
}
|
|
4361
|
-
if (input.
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
};
|
|
4368
|
-
const result = await whisper.addContext({
|
|
4369
|
-
project: resolvedProject,
|
|
4370
|
-
content: input.content,
|
|
4371
|
-
title: input.title || "Learned Context",
|
|
4372
|
-
metadata: mergedMetadata
|
|
4373
|
-
});
|
|
4374
|
-
return {
|
|
4375
|
-
mode: "text",
|
|
4376
|
-
project: resolvedProject,
|
|
4377
|
-
ingested: result.ingested
|
|
4378
|
-
};
|
|
4379
|
-
}
|
|
4380
|
-
if (input.owner && input.repo) {
|
|
4381
|
-
return createSourceByType({
|
|
4382
|
-
project: resolvedProject,
|
|
4383
|
-
type: "github",
|
|
4384
|
-
owner: input.owner,
|
|
4385
|
-
repo: input.repo,
|
|
4386
|
-
branch: input.branch,
|
|
4387
|
-
name: input.name,
|
|
4388
|
-
auto_index: true,
|
|
4389
|
-
metadata: input.metadata,
|
|
4390
|
-
ingestion_profile: input.ingestion_profile,
|
|
4391
|
-
strategy_override: input.strategy_override,
|
|
4392
|
-
profile_config: input.profile_config
|
|
4393
|
-
});
|
|
4394
|
-
}
|
|
4395
|
-
if (input.path) {
|
|
4396
|
-
return createSourceByType({
|
|
4397
|
-
project: resolvedProject,
|
|
4398
|
-
type: "local",
|
|
4399
|
-
path: input.path,
|
|
4400
|
-
glob: input.glob,
|
|
4401
|
-
max_files: input.max_files,
|
|
4402
|
-
name: input.name,
|
|
4403
|
-
metadata: input.metadata,
|
|
4404
|
-
ingestion_profile: input.ingestion_profile,
|
|
4405
|
-
strategy_override: input.strategy_override,
|
|
4406
|
-
profile_config: input.profile_config
|
|
4407
|
-
});
|
|
4408
|
-
}
|
|
4409
|
-
if (input.file_path) {
|
|
4410
|
-
return createSourceByType({
|
|
4813
|
+
if (input.mode === "conversation") {
|
|
4814
|
+
if (!input.session_id || !input.messages || input.messages.length === 0) {
|
|
4815
|
+
throw new Error("conversation learn requires session_id and messages[]");
|
|
4816
|
+
}
|
|
4817
|
+
return whisper.learn({
|
|
4818
|
+
mode: "conversation",
|
|
4411
4819
|
project: resolvedProject,
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
profile_config: input.profile_config
|
|
4820
|
+
user_id: input.user_id,
|
|
4821
|
+
session_id: input.session_id,
|
|
4822
|
+
messages: input.messages.map((message) => ({
|
|
4823
|
+
...message,
|
|
4824
|
+
timestamp: message.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
4825
|
+
}))
|
|
4419
4826
|
});
|
|
4420
4827
|
}
|
|
4421
|
-
if (input.
|
|
4422
|
-
|
|
4828
|
+
if (input.mode === "text") {
|
|
4829
|
+
if (!input.title || !input.content) {
|
|
4830
|
+
throw new Error("text learn requires title and content");
|
|
4831
|
+
}
|
|
4832
|
+
return whisper.learn({
|
|
4833
|
+
mode: "text",
|
|
4423
4834
|
project: resolvedProject,
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
name: input.name,
|
|
4835
|
+
title: input.title,
|
|
4836
|
+
content: input.content,
|
|
4427
4837
|
metadata: input.metadata,
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
crawl_depth: input.crawl_depth,
|
|
4432
|
-
channel_ids: input.channel_ids,
|
|
4433
|
-
token: input.token,
|
|
4434
|
-
workspace_id: input.workspace_id,
|
|
4435
|
-
since: input.since,
|
|
4436
|
-
auth_ref: input.auth_ref
|
|
4838
|
+
tags: input.tags,
|
|
4839
|
+
namespace: input.namespace,
|
|
4840
|
+
options: input.options
|
|
4437
4841
|
});
|
|
4438
4842
|
}
|
|
4439
|
-
|
|
4843
|
+
if (!input.type) {
|
|
4844
|
+
throw new Error("source learn requires type");
|
|
4845
|
+
}
|
|
4846
|
+
return whisper.learn({
|
|
4847
|
+
mode: "source",
|
|
4848
|
+
project: resolvedProject,
|
|
4849
|
+
type: input.type,
|
|
4850
|
+
name: input.name,
|
|
4851
|
+
metadata: input.metadata,
|
|
4852
|
+
owner: input.owner,
|
|
4853
|
+
repo: input.repo,
|
|
4854
|
+
branch: input.branch,
|
|
4855
|
+
paths: input.paths,
|
|
4856
|
+
url: input.url,
|
|
4857
|
+
file_path: input.file_path,
|
|
4858
|
+
path: input.path,
|
|
4859
|
+
channel_ids: input.channel_ids,
|
|
4860
|
+
since: input.since,
|
|
4861
|
+
token: input.token,
|
|
4862
|
+
auth_ref: input.auth_ref,
|
|
4863
|
+
platform: input.platform,
|
|
4864
|
+
language: input.language,
|
|
4865
|
+
options: input.options
|
|
4866
|
+
});
|
|
4440
4867
|
}
|
|
4441
4868
|
function renderScopedMcpConfig(project, source, client) {
|
|
4442
4869
|
const serverDef = {
|
|
@@ -4473,22 +4900,25 @@ server.tool(
|
|
|
4473
4900
|
},
|
|
4474
4901
|
async ({ path, workspace_id, project }) => {
|
|
4475
4902
|
try {
|
|
4476
|
-
const
|
|
4903
|
+
const rootPath = canonicalizeWorkspacePath(path);
|
|
4904
|
+
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4477
4905
|
const state = loadState();
|
|
4478
4906
|
const existed = Boolean(state.workspaces[workspaceId]);
|
|
4479
|
-
const
|
|
4480
|
-
const resolvedProject = await resolveProjectRef(project);
|
|
4481
|
-
const resolvedBy = project?.trim() ? "explicit_project" : DEFAULT_PROJECT ? "env_default" : resolvedProject ? "auto_first_project" : "unresolved";
|
|
4482
|
-
saveState(state);
|
|
4907
|
+
const trust = await resolveWorkspaceTrust({ path: rootPath, workspace_id, project });
|
|
4483
4908
|
const payload = {
|
|
4484
4909
|
workspace_id: workspaceId,
|
|
4485
|
-
|
|
4910
|
+
root_path: trust.root_path,
|
|
4911
|
+
project_ref: trust.project_ref,
|
|
4912
|
+
project_id: trust.project_id,
|
|
4486
4913
|
created: !existed,
|
|
4487
|
-
resolved_by:
|
|
4914
|
+
resolved_by: trust.resolved_by,
|
|
4915
|
+
health: trust.health,
|
|
4916
|
+
warnings: trust.warnings,
|
|
4917
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4488
4918
|
index_state: {
|
|
4489
|
-
last_indexed_at:
|
|
4490
|
-
last_indexed_commit:
|
|
4491
|
-
coverage:
|
|
4919
|
+
last_indexed_at: trust.freshness.last_indexed_at,
|
|
4920
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
4921
|
+
coverage: trust.coverage
|
|
4492
4922
|
}
|
|
4493
4923
|
};
|
|
4494
4924
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
@@ -4506,24 +4936,22 @@ server.tool(
|
|
|
4506
4936
|
},
|
|
4507
4937
|
async ({ workspace_id, path }) => {
|
|
4508
4938
|
try {
|
|
4509
|
-
const
|
|
4510
|
-
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4511
|
-
const state = loadState();
|
|
4512
|
-
const workspace = getWorkspaceState(state, workspaceId);
|
|
4513
|
-
const lastIndexedAt = workspace.index_metadata?.last_indexed_at;
|
|
4514
|
-
const ageHours = lastIndexedAt ? (Date.now() - new Date(lastIndexedAt).getTime()) / (60 * 60 * 1e3) : null;
|
|
4515
|
-
const stale = ageHours === null ? true : ageHours > 168;
|
|
4939
|
+
const trust = await resolveWorkspaceTrust({ path, workspace_id });
|
|
4516
4940
|
const payload = {
|
|
4517
|
-
workspace_id:
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4941
|
+
workspace_id: trust.workspace_id,
|
|
4942
|
+
root_path: trust.root_path,
|
|
4943
|
+
project_ref: trust.project_ref,
|
|
4944
|
+
project_id: trust.project_id,
|
|
4945
|
+
resolved_by: trust.resolved_by,
|
|
4946
|
+
health: trust.health,
|
|
4947
|
+
warnings: trust.warnings,
|
|
4948
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4949
|
+
freshness: trust.freshness,
|
|
4950
|
+
coverage: trust.coverage,
|
|
4951
|
+
last_indexed_commit: trust.last_indexed_commit,
|
|
4952
|
+
current_commit: trust.current_commit,
|
|
4953
|
+
pending_changes: trust.pending_changes,
|
|
4954
|
+
grounded_to_workspace: trust.grounded_to_workspace
|
|
4527
4955
|
};
|
|
4528
4956
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4529
4957
|
} catch (error) {
|
|
@@ -4542,13 +4970,14 @@ server.tool(
|
|
|
4542
4970
|
},
|
|
4543
4971
|
async ({ workspace_id, path, mode, max_files }) => {
|
|
4544
4972
|
try {
|
|
4545
|
-
const rootPath = path
|
|
4973
|
+
const rootPath = canonicalizeWorkspacePath(path);
|
|
4546
4974
|
const workspaceId = getWorkspaceIdForPath(rootPath, workspace_id);
|
|
4547
4975
|
const state = loadState();
|
|
4548
4976
|
const workspace = getWorkspaceState(state, workspaceId);
|
|
4549
4977
|
const fileStats = countCodeFiles(rootPath, max_files);
|
|
4550
4978
|
const coverage = Math.max(0, Math.min(1, fileStats.total / Math.max(1, max_files)));
|
|
4551
4979
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4980
|
+
workspace.root_path = rootPath;
|
|
4552
4981
|
workspace.index_metadata = {
|
|
4553
4982
|
last_indexed_at: now,
|
|
4554
4983
|
last_indexed_commit: getGitHead(rootPath),
|
|
@@ -4557,6 +4986,7 @@ server.tool(
|
|
|
4557
4986
|
saveState(state);
|
|
4558
4987
|
const payload = {
|
|
4559
4988
|
workspace_id: workspaceId,
|
|
4989
|
+
root_path: rootPath,
|
|
4560
4990
|
mode,
|
|
4561
4991
|
indexed_files: fileStats.total,
|
|
4562
4992
|
skipped_files: fileStats.skipped,
|
|
@@ -4621,23 +5051,25 @@ server.tool(
|
|
|
4621
5051
|
},
|
|
4622
5052
|
async ({ question, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id }) => {
|
|
4623
5053
|
try {
|
|
4624
|
-
const
|
|
4625
|
-
|
|
4626
|
-
if (!resolvedProject) {
|
|
5054
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project });
|
|
5055
|
+
if (trust.health === "unbound" || trust.health === "unindexed" || !trust.project_ref) {
|
|
4627
5056
|
const payload2 = {
|
|
4628
5057
|
question,
|
|
4629
|
-
workspace_id:
|
|
5058
|
+
workspace_id: trust.workspace_id,
|
|
5059
|
+
trust_state: trust,
|
|
5060
|
+
grounded_to_workspace: false,
|
|
4630
5061
|
total_results: 0,
|
|
4631
5062
|
context: "",
|
|
4632
5063
|
evidence: [],
|
|
4633
5064
|
used_context_ids: [],
|
|
4634
5065
|
latency_ms: 0,
|
|
4635
|
-
|
|
5066
|
+
warnings: trust.warnings,
|
|
5067
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4636
5068
|
};
|
|
4637
5069
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4638
5070
|
}
|
|
4639
5071
|
const queryResult = await queryWithDegradedFallback({
|
|
4640
|
-
project:
|
|
5072
|
+
project: trust.project_ref,
|
|
4641
5073
|
query: question,
|
|
4642
5074
|
top_k,
|
|
4643
5075
|
include_memories,
|
|
@@ -4646,10 +5078,12 @@ server.tool(
|
|
|
4646
5078
|
user_id
|
|
4647
5079
|
});
|
|
4648
5080
|
const response = queryResult.response;
|
|
4649
|
-
const evidence = (response.results || []).map((r) => toEvidenceRef(r,
|
|
5081
|
+
const evidence = (response.results || []).map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4650
5082
|
const payload = {
|
|
4651
5083
|
question,
|
|
4652
|
-
workspace_id:
|
|
5084
|
+
workspace_id: trust.workspace_id,
|
|
5085
|
+
trust_state: trust,
|
|
5086
|
+
grounded_to_workspace: trust.grounded_to_workspace,
|
|
4653
5087
|
total_results: response.meta?.total || evidence.length,
|
|
4654
5088
|
context: response.context || "",
|
|
4655
5089
|
evidence,
|
|
@@ -4657,7 +5091,9 @@ server.tool(
|
|
|
4657
5091
|
latency_ms: response.meta?.latency_ms || 0,
|
|
4658
5092
|
degraded_mode: queryResult.degraded_mode,
|
|
4659
5093
|
degraded_reason: queryResult.degraded_reason,
|
|
4660
|
-
recommendation: queryResult.recommendation
|
|
5094
|
+
recommendation: queryResult.recommendation,
|
|
5095
|
+
warnings: trust.warnings,
|
|
5096
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4661
5097
|
};
|
|
4662
5098
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
4663
5099
|
} catch (error) {
|
|
@@ -4677,20 +5113,22 @@ server.tool(
|
|
|
4677
5113
|
},
|
|
4678
5114
|
async ({ claim, workspace_id, project, context_ids, strict }) => {
|
|
4679
5115
|
try {
|
|
4680
|
-
const
|
|
4681
|
-
|
|
4682
|
-
if (!resolvedProject) {
|
|
5116
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project });
|
|
5117
|
+
if (trust.health !== "healthy" || !trust.project_ref) {
|
|
4683
5118
|
const payload2 = {
|
|
4684
5119
|
verdict: "unsupported",
|
|
4685
5120
|
confidence: 0,
|
|
4686
5121
|
evidence: [],
|
|
4687
|
-
|
|
4688
|
-
|
|
5122
|
+
trust_state: trust,
|
|
5123
|
+
warnings: trust.warnings,
|
|
5124
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
5125
|
+
missing_requirements: trust.warnings.length ? trust.warnings : ["Workspace trust requirements were not met."],
|
|
5126
|
+
explanation: "Verifier did not run because workspace grounding is insufficient."
|
|
4689
5127
|
};
|
|
4690
5128
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
4691
5129
|
}
|
|
4692
5130
|
const response = await whisper.query({
|
|
4693
|
-
project:
|
|
5131
|
+
project: trust.project_ref,
|
|
4694
5132
|
query: claim,
|
|
4695
5133
|
top_k: strict ? 8 : 12,
|
|
4696
5134
|
include_memories: true,
|
|
@@ -4699,7 +5137,7 @@ server.tool(
|
|
|
4699
5137
|
const filtered = (response.results || []).filter(
|
|
4700
5138
|
(r) => !context_ids || context_ids.length === 0 || context_ids.includes(String(r.id))
|
|
4701
5139
|
);
|
|
4702
|
-
const evidence = filtered.map((r) => toEvidenceRef(r,
|
|
5140
|
+
const evidence = filtered.map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4703
5141
|
const directEvidence = evidence.filter((e) => e.score >= (strict ? 0.7 : 0.6));
|
|
4704
5142
|
const weakEvidence = evidence.filter((e) => e.score >= (strict ? 0.45 : 0.35));
|
|
4705
5143
|
let verdict = "unsupported";
|
|
@@ -4709,6 +5147,9 @@ server.tool(
|
|
|
4709
5147
|
verdict,
|
|
4710
5148
|
confidence: evidence.length ? Math.max(...evidence.map((e) => e.score)) : 0,
|
|
4711
5149
|
evidence: verdict === "supported" ? directEvidence : weakEvidence,
|
|
5150
|
+
trust_state: trust,
|
|
5151
|
+
warnings: trust.warnings,
|
|
5152
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4712
5153
|
missing_requirements: verdict === "supported" ? [] : verdict === "partial" ? ["No direct evidence spans met strict threshold."] : ["No sufficient supporting evidence found for the claim."],
|
|
4713
5154
|
explanation: verdict === "supported" ? "At least one direct evidence span supports the claim." : verdict === "partial" ? "Some related evidence exists, but direct support is incomplete." : "Retrieved context did not contain sufficient support."
|
|
4714
5155
|
};
|
|
@@ -4739,39 +5180,38 @@ server.tool(
|
|
|
4739
5180
|
},
|
|
4740
5181
|
async ({ question, workspace_id, project, constraints, retrieval }) => {
|
|
4741
5182
|
try {
|
|
4742
|
-
const workspaceId = getWorkspaceId(workspace_id);
|
|
4743
5183
|
const requireCitations = constraints?.require_citations ?? true;
|
|
4744
5184
|
const minEvidenceItems = constraints?.min_evidence_items ?? 2;
|
|
4745
5185
|
const minConfidence = constraints?.min_confidence ?? 0.65;
|
|
4746
5186
|
const maxStalenessHours = constraints?.max_staleness_hours ?? 168;
|
|
4747
5187
|
const topK = retrieval?.top_k ?? 12;
|
|
4748
|
-
const
|
|
4749
|
-
if (!
|
|
5188
|
+
const trust = await resolveWorkspaceTrust({ workspace_id, project, max_staleness_hours: maxStalenessHours });
|
|
5189
|
+
if (trust.health !== "healthy" || !trust.project_ref) {
|
|
5190
|
+
const reason = trust.health === "stale" || trust.health === "drifted" ? "stale_index" : "no_retrieval_hits";
|
|
4750
5191
|
const abstain = buildAbstain({
|
|
4751
|
-
reason
|
|
4752
|
-
message:
|
|
5192
|
+
reason,
|
|
5193
|
+
message: trust.warnings[0] || "Workspace trust requirements were not met.",
|
|
4753
5194
|
closest_evidence: [],
|
|
4754
5195
|
claims_evaluated: 1,
|
|
4755
5196
|
evidence_items_found: 0,
|
|
4756
5197
|
min_required: minEvidenceItems,
|
|
4757
|
-
index_fresh:
|
|
5198
|
+
index_fresh: trust.health === "healthy",
|
|
5199
|
+
warnings: trust.warnings,
|
|
5200
|
+
trust_state: trust,
|
|
5201
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4758
5202
|
});
|
|
4759
5203
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4760
5204
|
}
|
|
4761
5205
|
const response = await whisper.query({
|
|
4762
|
-
project:
|
|
5206
|
+
project: trust.project_ref,
|
|
4763
5207
|
query: question,
|
|
4764
5208
|
top_k: topK,
|
|
4765
5209
|
include_memories: true,
|
|
4766
5210
|
include_graph: true
|
|
4767
5211
|
});
|
|
4768
|
-
const evidence = (response.results || []).map((r) => toEvidenceRef(r,
|
|
5212
|
+
const evidence = (response.results || []).map((r) => toEvidenceRef(r, trust.workspace_id, "semantic"));
|
|
4769
5213
|
const sorted = evidence.sort((a, b) => b.score - a.score);
|
|
4770
5214
|
const confidence = sorted.length ? sorted[0].score : 0;
|
|
4771
|
-
const state = loadState();
|
|
4772
|
-
const workspace = getWorkspaceState(state, workspaceId);
|
|
4773
|
-
const lastIndexedAt = workspace.index_metadata?.last_indexed_at;
|
|
4774
|
-
const indexFresh = !lastIndexedAt || Date.now() - new Date(lastIndexedAt).getTime() <= maxStalenessHours * 60 * 60 * 1e3;
|
|
4775
5215
|
if (sorted.length === 0) {
|
|
4776
5216
|
const abstain = buildAbstain({
|
|
4777
5217
|
reason: "no_retrieval_hits",
|
|
@@ -4780,24 +5220,15 @@ server.tool(
|
|
|
4780
5220
|
claims_evaluated: 1,
|
|
4781
5221
|
evidence_items_found: 0,
|
|
4782
5222
|
min_required: minEvidenceItems,
|
|
4783
|
-
index_fresh:
|
|
5223
|
+
index_fresh: trust.health === "healthy",
|
|
5224
|
+
warnings: trust.warnings,
|
|
5225
|
+
trust_state: trust,
|
|
5226
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4784
5227
|
});
|
|
4785
5228
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4786
5229
|
}
|
|
4787
5230
|
const supportedEvidence = sorted.filter((e) => e.score >= minConfidence);
|
|
4788
5231
|
const verdict = supportedEvidence.length >= 1 ? "supported" : "partial";
|
|
4789
|
-
if (!indexFresh) {
|
|
4790
|
-
const abstain = buildAbstain({
|
|
4791
|
-
reason: "stale_index",
|
|
4792
|
-
message: "Index freshness requirement not met. Re-index before answering.",
|
|
4793
|
-
closest_evidence: sorted.slice(0, 3),
|
|
4794
|
-
claims_evaluated: 1,
|
|
4795
|
-
evidence_items_found: supportedEvidence.length,
|
|
4796
|
-
min_required: minEvidenceItems,
|
|
4797
|
-
index_fresh: false
|
|
4798
|
-
});
|
|
4799
|
-
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4800
|
-
}
|
|
4801
5232
|
if (requireCitations && (verdict !== "supported" || supportedEvidence.length < minEvidenceItems)) {
|
|
4802
5233
|
const abstain = buildAbstain({
|
|
4803
5234
|
reason: "insufficient_evidence",
|
|
@@ -4806,7 +5237,10 @@ server.tool(
|
|
|
4806
5237
|
claims_evaluated: 1,
|
|
4807
5238
|
evidence_items_found: supportedEvidence.length,
|
|
4808
5239
|
min_required: minEvidenceItems,
|
|
4809
|
-
index_fresh: true
|
|
5240
|
+
index_fresh: true,
|
|
5241
|
+
warnings: trust.warnings,
|
|
5242
|
+
trust_state: trust,
|
|
5243
|
+
recommended_next_calls: trust.recommended_next_calls
|
|
4810
5244
|
});
|
|
4811
5245
|
return { content: [{ type: "text", text: JSON.stringify(abstain, null, 2) }] };
|
|
4812
5246
|
}
|
|
@@ -4819,6 +5253,9 @@ server.tool(
|
|
|
4819
5253
|
answer: answerLines.join("\n"),
|
|
4820
5254
|
citations,
|
|
4821
5255
|
confidence,
|
|
5256
|
+
trust_state: trust,
|
|
5257
|
+
warnings: trust.warnings,
|
|
5258
|
+
recommended_next_calls: trust.recommended_next_calls,
|
|
4822
5259
|
verification: {
|
|
4823
5260
|
verdict,
|
|
4824
5261
|
supported_claims: verdict === "supported" ? 1 : 0,
|
|
@@ -4852,6 +5289,7 @@ server.tool(
|
|
|
4852
5289
|
if (!resolvedProject) {
|
|
4853
5290
|
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
|
|
4854
5291
|
}
|
|
5292
|
+
const scope = resolveMcpScope({ project: resolvedProject, user_id, session_id });
|
|
4855
5293
|
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4856
5294
|
if (automaticMode) {
|
|
4857
5295
|
try {
|
|
@@ -4863,6 +5301,27 @@ server.tool(
|
|
|
4863
5301
|
session_id
|
|
4864
5302
|
});
|
|
4865
5303
|
if (!prepared.items.length) {
|
|
5304
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5305
|
+
project: resolvedProject,
|
|
5306
|
+
query,
|
|
5307
|
+
user_id: user_id ? scope2.userId : void 0,
|
|
5308
|
+
session_id: session_id ? scope2.sessionId : void 0,
|
|
5309
|
+
top_k
|
|
5310
|
+
}) : { results: [], rescue_mode: null };
|
|
5311
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5312
|
+
return {
|
|
5313
|
+
content: [{
|
|
5314
|
+
type: "text",
|
|
5315
|
+
text: renderContextQueryMemoryRescue({
|
|
5316
|
+
project: resolvedProject,
|
|
5317
|
+
query,
|
|
5318
|
+
scope: scope2,
|
|
5319
|
+
results: memoryRescue.results,
|
|
5320
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5321
|
+
})
|
|
5322
|
+
}]
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
4866
5325
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4867
5326
|
}
|
|
4868
5327
|
const warnings = prepared.retrieval.warnings.length ? `
|
|
@@ -4897,13 +5356,36 @@ ${prepared.context}${warnings}`
|
|
|
4897
5356
|
});
|
|
4898
5357
|
const response2 = queryResult2.response;
|
|
4899
5358
|
if (response2.results.length === 0) {
|
|
5359
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5360
|
+
project: resolvedProject,
|
|
5361
|
+
query,
|
|
5362
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5363
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5364
|
+
top_k
|
|
5365
|
+
}) : { results: [], rescue_mode: null };
|
|
5366
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5367
|
+
return {
|
|
5368
|
+
content: [{
|
|
5369
|
+
type: "text",
|
|
5370
|
+
text: `${renderContextQueryMemoryRescue({
|
|
5371
|
+
project: resolvedProject,
|
|
5372
|
+
query,
|
|
5373
|
+
scope,
|
|
5374
|
+
results: memoryRescue.results,
|
|
5375
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5376
|
+
})}
|
|
5377
|
+
|
|
5378
|
+
[automatic_runtime]
|
|
5379
|
+
${automaticWarning}`
|
|
5380
|
+
}]
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
4900
5383
|
return { content: [{ type: "text", text: `No relevant context found.
|
|
4901
5384
|
|
|
4902
5385
|
[automatic_runtime]
|
|
4903
5386
|
${automaticWarning}` }] };
|
|
4904
5387
|
}
|
|
4905
|
-
const
|
|
4906
|
-
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}):
|
|
5388
|
+
const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
4907
5389
|
|
|
4908
5390
|
`;
|
|
4909
5391
|
const suffix2 = queryResult2.degraded_mode ? `
|
|
@@ -4927,9 +5409,29 @@ ${automaticWarning}${suffix2}` }] };
|
|
|
4927
5409
|
});
|
|
4928
5410
|
const response = queryResult.response;
|
|
4929
5411
|
if (response.results.length === 0) {
|
|
5412
|
+
const memoryRescue = include_memories !== false ? await runContextQueryMemoryRescue({
|
|
5413
|
+
project: resolvedProject,
|
|
5414
|
+
query,
|
|
5415
|
+
user_id: user_id ? scope.userId : void 0,
|
|
5416
|
+
session_id: session_id ? scope.sessionId : void 0,
|
|
5417
|
+
top_k
|
|
5418
|
+
}) : { results: [], rescue_mode: null };
|
|
5419
|
+
if (memoryRescue.results.length && memoryRescue.rescue_mode) {
|
|
5420
|
+
return {
|
|
5421
|
+
content: [{
|
|
5422
|
+
type: "text",
|
|
5423
|
+
text: renderContextQueryMemoryRescue({
|
|
5424
|
+
project: resolvedProject,
|
|
5425
|
+
query,
|
|
5426
|
+
scope,
|
|
5427
|
+
results: memoryRescue.results,
|
|
5428
|
+
rescue_mode: memoryRescue.rescue_mode
|
|
5429
|
+
})
|
|
5430
|
+
}]
|
|
5431
|
+
};
|
|
5432
|
+
}
|
|
4930
5433
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4931
5434
|
}
|
|
4932
|
-
const scope = resolveMcpScope({ user_id, session_id });
|
|
4933
5435
|
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}):
|
|
4934
5436
|
|
|
4935
5437
|
`;
|
|
@@ -5067,10 +5569,10 @@ server.tool(
|
|
|
5067
5569
|
);
|
|
5068
5570
|
server.tool(
|
|
5069
5571
|
"context.add_source",
|
|
5070
|
-
"Add a source to a project with normalized source contract and auto-index by default.",
|
|
5572
|
+
"Compatibility learning tool. Add a source to a project with normalized source contract and auto-index by default. Prefer `learn` for new integrations.",
|
|
5071
5573
|
{
|
|
5072
5574
|
project: z.string().optional().describe("Project name or slug"),
|
|
5073
|
-
type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).default("github"),
|
|
5575
|
+
type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).default("github"),
|
|
5074
5576
|
name: z.string().optional(),
|
|
5075
5577
|
auto_index: z.boolean().optional().default(true),
|
|
5076
5578
|
metadata: z.record(z.string()).optional(),
|
|
@@ -5089,6 +5591,8 @@ server.tool(
|
|
|
5089
5591
|
path: z.string().optional(),
|
|
5090
5592
|
glob: z.string().optional(),
|
|
5091
5593
|
max_files: z.number().optional(),
|
|
5594
|
+
max_pages: z.number().optional(),
|
|
5595
|
+
extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
|
|
5092
5596
|
workspace_id: z.string().optional(),
|
|
5093
5597
|
channel_ids: z.array(z.string()).optional(),
|
|
5094
5598
|
since: z.string().optional(),
|
|
@@ -5127,6 +5631,8 @@ server.tool(
|
|
|
5127
5631
|
path: input.path,
|
|
5128
5632
|
glob: input.glob,
|
|
5129
5633
|
max_files: input.max_files,
|
|
5634
|
+
max_pages: input.max_pages,
|
|
5635
|
+
extract_mode: input.extract_mode,
|
|
5130
5636
|
workspace_id: input.workspace_id,
|
|
5131
5637
|
channel_ids: input.channel_ids,
|
|
5132
5638
|
since: input.since,
|
|
@@ -5165,7 +5671,7 @@ server.tool(
|
|
|
5165
5671
|
);
|
|
5166
5672
|
server.tool(
|
|
5167
5673
|
"context.add_text",
|
|
5168
|
-
"Add text content to a project's knowledge base.",
|
|
5674
|
+
"Compatibility learning tool. Add text content to a project's knowledge base. Prefer `learn` for new integrations.",
|
|
5169
5675
|
{
|
|
5170
5676
|
project: z.string().optional().describe("Project name or slug"),
|
|
5171
5677
|
title: z.string().describe("Title for this content"),
|
|
@@ -5198,7 +5704,7 @@ server.tool(
|
|
|
5198
5704
|
);
|
|
5199
5705
|
server.tool(
|
|
5200
5706
|
"context.add_document",
|
|
5201
|
-
"Ingest a document into project knowledge. Supports plain text and video URLs.",
|
|
5707
|
+
"Compatibility learning tool. Ingest a document into project knowledge. Supports plain text and video URLs. Prefer `learn` for new integrations.",
|
|
5202
5708
|
{
|
|
5203
5709
|
project: z.string().optional().describe("Project name or slug"),
|
|
5204
5710
|
source_type: z.enum(["text", "video"]).default("text"),
|
|
@@ -5303,7 +5809,7 @@ server.tool(
|
|
|
5303
5809
|
);
|
|
5304
5810
|
server.tool(
|
|
5305
5811
|
"memory.ingest_conversation",
|
|
5306
|
-
"Extract memories from a conversation session.
|
|
5812
|
+
"Compatibility learning tool. Extract memories from a conversation session. Prefer `learn` for new integrations.",
|
|
5307
5813
|
{
|
|
5308
5814
|
project: z.string().optional().describe("Project name or slug"),
|
|
5309
5815
|
session_id: z.string().describe("Session identifier"),
|
|
@@ -5568,6 +6074,7 @@ server.tool(
|
|
|
5568
6074
|
return { content: [{ type: "text", text: "Error: target.memory_id or target.query is required." }] };
|
|
5569
6075
|
}
|
|
5570
6076
|
const affectedIds = [];
|
|
6077
|
+
let queryResolution = null;
|
|
5571
6078
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5572
6079
|
const actor = process.env.WHISPER_AGENT_ID || process.env.USERNAME || "api_key_principal";
|
|
5573
6080
|
const resolvedProject = await resolveProjectRef(project);
|
|
@@ -5626,24 +6133,18 @@ server.tool(
|
|
|
5626
6133
|
project: resolvedProject,
|
|
5627
6134
|
query: target.query || "",
|
|
5628
6135
|
top_k: 25,
|
|
5629
|
-
include_relations: false
|
|
5630
|
-
|
|
5631
|
-
const normalizedQuery = String(target.query || "").trim().toLowerCase();
|
|
5632
|
-
const exactMatches = (search.results || []).filter((r) => {
|
|
5633
|
-
const memory = r?.memory || r;
|
|
5634
|
-
const content = String(memory?.content || "").trim().toLowerCase();
|
|
5635
|
-
const memoryId = String(memory?.id || "").trim().toLowerCase();
|
|
5636
|
-
const metadata = memory?.metadata || {};
|
|
5637
|
-
const normalizedContent = String(metadata?.normalized_content || "").trim().toLowerCase();
|
|
5638
|
-
const canonicalContent = String(metadata?.canonical_content || "").trim().toLowerCase();
|
|
5639
|
-
return memoryId === normalizedQuery || content === normalizedQuery || normalizedContent === normalizedQuery || canonicalContent === normalizedQuery;
|
|
6136
|
+
include_relations: false,
|
|
6137
|
+
include_pending: true
|
|
5640
6138
|
});
|
|
5641
|
-
const
|
|
6139
|
+
const resolved = resolveForgetQueryCandidates(search, target.query || "");
|
|
6140
|
+
queryResolution = resolved.resolved_by;
|
|
6141
|
+
const memoryIds = resolved.memory_ids;
|
|
5642
6142
|
if (memoryIds.length === 0) {
|
|
5643
6143
|
const payload2 = {
|
|
5644
6144
|
status: "completed",
|
|
5645
6145
|
affected_ids: affectedIds,
|
|
5646
|
-
warning: "Query did not resolve to
|
|
6146
|
+
warning: resolved.warning || "Query did not resolve to a reliable memory match. No memories were changed.",
|
|
6147
|
+
resolved_by: resolved.resolved_by
|
|
5647
6148
|
};
|
|
5648
6149
|
return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
|
|
5649
6150
|
}
|
|
@@ -5665,6 +6166,7 @@ server.tool(
|
|
|
5665
6166
|
const payload = {
|
|
5666
6167
|
status: "completed",
|
|
5667
6168
|
affected_ids: affectedIds,
|
|
6169
|
+
...queryResolution ? { resolved_by: queryResolution } : {},
|
|
5668
6170
|
audit: {
|
|
5669
6171
|
audit_id: audit.audit_id,
|
|
5670
6172
|
actor: audit.actor,
|
|
@@ -5959,30 +6461,45 @@ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"
|
|
|
5959
6461
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "py", "go", "rs", "java", "cpp", "c", "cs", "rb", "php", "swift", "kt", "sql", "prisma", "graphql", "json", "yaml", "yml", "toml", "env"]);
|
|
5960
6462
|
function extractSignature(filePath, content) {
|
|
5961
6463
|
const lines = content.split("\n");
|
|
5962
|
-
const signature = [`// File: ${filePath}`];
|
|
6464
|
+
const signature = /* @__PURE__ */ new Set([`// File: ${filePath}`]);
|
|
6465
|
+
for (const segment of filePath.split(/[\\/._-]+/).filter(Boolean)) {
|
|
6466
|
+
signature.add(`path:${segment}`);
|
|
6467
|
+
}
|
|
5963
6468
|
const head = lines.slice(0, 60);
|
|
5964
6469
|
for (const line of head) {
|
|
5965
6470
|
const trimmed = line.trim();
|
|
5966
6471
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
6472
|
+
const urlMatches = trimmed.match(/https?:\/\/[^\s"'`]+/g);
|
|
6473
|
+
if (urlMatches) {
|
|
6474
|
+
for (const match of urlMatches.slice(0, 2)) signature.add(`url:${match.slice(0, 120)}`);
|
|
6475
|
+
}
|
|
6476
|
+
const routeMatches = trimmed.match(/\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{2,}/g);
|
|
6477
|
+
if (routeMatches) {
|
|
6478
|
+
for (const match of routeMatches.slice(0, 3)) signature.add(`route:${match.slice(0, 120)}`);
|
|
6479
|
+
}
|
|
5967
6480
|
if (/^(import|from|require|use |pub use )/.test(trimmed)) {
|
|
5968
|
-
signature.
|
|
6481
|
+
signature.add(trimmed.slice(0, 120));
|
|
5969
6482
|
continue;
|
|
5970
6483
|
}
|
|
5971
6484
|
if (/^(export|async function|function|class|interface|type |const |let |def |pub fn |fn |struct |impl |enum )/.test(trimmed)) {
|
|
5972
|
-
signature.
|
|
6485
|
+
signature.add(trimmed.slice(0, 120));
|
|
5973
6486
|
continue;
|
|
5974
6487
|
}
|
|
5975
6488
|
if (trimmed.startsWith("@") || trimmed.startsWith("#[")) {
|
|
5976
|
-
signature.
|
|
6489
|
+
signature.add(trimmed.slice(0, 80));
|
|
5977
6490
|
}
|
|
5978
6491
|
}
|
|
5979
6492
|
for (const line of lines.slice(60)) {
|
|
5980
6493
|
const trimmed = line.trim();
|
|
5981
6494
|
if (/^(export (default |async )?function|export (default )?class|export const|export type|export interface|async function|function |class |def |pub fn |fn )/.test(trimmed)) {
|
|
5982
|
-
signature.
|
|
6495
|
+
signature.add(trimmed.slice(0, 120));
|
|
6496
|
+
continue;
|
|
6497
|
+
}
|
|
6498
|
+
if (/^[A-Za-z0-9_$]+\s*[:=]\s*["'`][^"'`]{3,}["'`]/.test(trimmed)) {
|
|
6499
|
+
signature.add(trimmed.slice(0, 120));
|
|
5983
6500
|
}
|
|
5984
6501
|
}
|
|
5985
|
-
return signature.join("\n").slice(0,
|
|
6502
|
+
return Array.from(signature).join("\n").slice(0, 2500);
|
|
5986
6503
|
}
|
|
5987
6504
|
server.tool(
|
|
5988
6505
|
"code.search_semantic",
|
|
@@ -5996,91 +6513,21 @@ server.tool(
|
|
|
5996
6513
|
max_files: z.number().optional().default(150).describe("Max files to scan. For large codebases, narrow with file_types instead of raising this.")
|
|
5997
6514
|
},
|
|
5998
6515
|
async ({ query, path: searchPath, file_types, top_k, threshold, max_files }) => {
|
|
5999
|
-
const rootPath = searchPath || process.cwd();
|
|
6000
|
-
const allowedExts = file_types ? new Set(file_types) : CODE_EXTENSIONS;
|
|
6001
|
-
const files = [];
|
|
6002
|
-
function collect(dir) {
|
|
6003
|
-
if (files.length >= (max_files ?? 300)) return;
|
|
6004
|
-
let entries;
|
|
6005
|
-
try {
|
|
6006
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
6007
|
-
} catch {
|
|
6008
|
-
return;
|
|
6009
|
-
}
|
|
6010
|
-
for (const entry of entries) {
|
|
6011
|
-
if (files.length >= (max_files ?? 300)) break;
|
|
6012
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
6013
|
-
const full = join(dir, entry.name);
|
|
6014
|
-
if (entry.isDirectory()) {
|
|
6015
|
-
collect(full);
|
|
6016
|
-
} else if (entry.isFile()) {
|
|
6017
|
-
const ext = extname(entry.name).replace(".", "");
|
|
6018
|
-
if (allowedExts.has(ext)) files.push(full);
|
|
6019
|
-
}
|
|
6020
|
-
}
|
|
6021
|
-
}
|
|
6022
|
-
collect(rootPath);
|
|
6023
|
-
if (files.length === 0) {
|
|
6024
|
-
return { content: [{ type: "text", text: `No code files found in ${rootPath}` }] };
|
|
6025
|
-
}
|
|
6026
|
-
const documents = [];
|
|
6027
|
-
for (const filePath of files) {
|
|
6028
|
-
try {
|
|
6029
|
-
const stat = statSync(filePath);
|
|
6030
|
-
if (stat.size > 500 * 1024) continue;
|
|
6031
|
-
const content = readFileSync(filePath, "utf-8");
|
|
6032
|
-
const relPath = relative(rootPath, filePath);
|
|
6033
|
-
const signature = extractSignature(relPath, content);
|
|
6034
|
-
documents.push({ id: relPath, content: signature });
|
|
6035
|
-
} catch {
|
|
6036
|
-
}
|
|
6037
|
-
}
|
|
6038
|
-
if (documents.length === 0) {
|
|
6039
|
-
return { content: [{ type: "text", text: "Could not read any files." }] };
|
|
6040
|
-
}
|
|
6041
|
-
let response;
|
|
6042
6516
|
try {
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6517
|
+
return primaryToolSuccess({
|
|
6518
|
+
...await runSearchCodeTool({
|
|
6519
|
+
query,
|
|
6520
|
+
path: searchPath,
|
|
6521
|
+
file_types,
|
|
6522
|
+
top_k,
|
|
6523
|
+
threshold,
|
|
6524
|
+
max_files
|
|
6525
|
+
}),
|
|
6526
|
+
tool: "code.search_semantic"
|
|
6048
6527
|
});
|
|
6049
6528
|
} catch (error) {
|
|
6050
|
-
return
|
|
6051
|
-
}
|
|
6052
|
-
if (!response.results || response.results.length === 0) {
|
|
6053
|
-
return { content: [{ type: "text", text: `No semantically relevant files found for: "${query}"
|
|
6054
|
-
|
|
6055
|
-
Searched ${documents.length} files in ${rootPath}.
|
|
6056
|
-
|
|
6057
|
-
Try lowering the threshold or rephrasing your query.` }] };
|
|
6058
|
-
}
|
|
6059
|
-
const lines = [
|
|
6060
|
-
`Semantic search: "${query}"`,
|
|
6061
|
-
`Searched ${documents.length} files \u2192 ${response.results.length} relevant (${response.latency_ms}ms)
|
|
6062
|
-
`
|
|
6063
|
-
];
|
|
6064
|
-
for (const result of response.results) {
|
|
6065
|
-
lines.push(`\u{1F4C4} ${result.id} (score: ${result.score})`);
|
|
6066
|
-
if (result.snippet) {
|
|
6067
|
-
lines.push(` ${result.snippet}`);
|
|
6068
|
-
}
|
|
6069
|
-
if (result.score > 0.5) {
|
|
6070
|
-
try {
|
|
6071
|
-
const fullPath = join(rootPath, result.id);
|
|
6072
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
6073
|
-
const excerpt = content.split("\n").slice(0, 30).join("\n");
|
|
6074
|
-
lines.push(`
|
|
6075
|
-
\`\`\`
|
|
6076
|
-
${excerpt}
|
|
6077
|
-
\`\`\``);
|
|
6078
|
-
} catch {
|
|
6079
|
-
}
|
|
6080
|
-
}
|
|
6081
|
-
lines.push("");
|
|
6529
|
+
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6082
6530
|
}
|
|
6083
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
6084
6531
|
}
|
|
6085
6532
|
);
|
|
6086
6533
|
function* walkDir(dir, fileTypes) {
|
|
@@ -6397,72 +6844,15 @@ server.tool(
|
|
|
6397
6844
|
max_files: z.number().optional().default(150)
|
|
6398
6845
|
},
|
|
6399
6846
|
async ({ query, path, file_types, top_k, threshold, max_files }) => {
|
|
6400
|
-
const rootPath = path || process.cwd();
|
|
6401
|
-
const allowedExts = file_types ? new Set(file_types) : CODE_EXTENSIONS;
|
|
6402
|
-
const files = [];
|
|
6403
|
-
function collect(dir) {
|
|
6404
|
-
if (files.length >= (max_files ?? 150)) return;
|
|
6405
|
-
let entries;
|
|
6406
|
-
try {
|
|
6407
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
6408
|
-
} catch {
|
|
6409
|
-
return;
|
|
6410
|
-
}
|
|
6411
|
-
for (const entry of entries) {
|
|
6412
|
-
if (files.length >= (max_files ?? 150)) break;
|
|
6413
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
6414
|
-
const full = join(dir, entry.name);
|
|
6415
|
-
if (entry.isDirectory()) collect(full);
|
|
6416
|
-
else if (entry.isFile()) {
|
|
6417
|
-
const ext = extname(entry.name).replace(".", "");
|
|
6418
|
-
if (allowedExts.has(ext)) files.push(full);
|
|
6419
|
-
}
|
|
6420
|
-
}
|
|
6421
|
-
}
|
|
6422
|
-
collect(rootPath);
|
|
6423
|
-
if (files.length === 0) {
|
|
6424
|
-
return primaryToolSuccess({
|
|
6425
|
-
tool: "search_code",
|
|
6426
|
-
query,
|
|
6427
|
-
path: rootPath,
|
|
6428
|
-
results: [],
|
|
6429
|
-
count: 0
|
|
6430
|
-
});
|
|
6431
|
-
}
|
|
6432
|
-
const documents = [];
|
|
6433
|
-
for (const filePath of files) {
|
|
6434
|
-
try {
|
|
6435
|
-
const stat = statSync(filePath);
|
|
6436
|
-
if (stat.size > 500 * 1024) continue;
|
|
6437
|
-
const content = readFileSync(filePath, "utf-8");
|
|
6438
|
-
const relPath = relative(rootPath, filePath);
|
|
6439
|
-
documents.push({ id: relPath, content: extractSignature(relPath, content) });
|
|
6440
|
-
} catch {
|
|
6441
|
-
}
|
|
6442
|
-
}
|
|
6443
6847
|
try {
|
|
6444
|
-
|
|
6848
|
+
return primaryToolSuccess(await runSearchCodeTool({
|
|
6445
6849
|
query,
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
tool: "search_code",
|
|
6453
|
-
query,
|
|
6454
|
-
path: rootPath,
|
|
6455
|
-
results: [],
|
|
6456
|
-
count: 0
|
|
6457
|
-
});
|
|
6458
|
-
}
|
|
6459
|
-
return primaryToolSuccess({
|
|
6460
|
-
tool: "search_code",
|
|
6461
|
-
query,
|
|
6462
|
-
path: rootPath,
|
|
6463
|
-
results: response.results,
|
|
6464
|
-
count: response.results.length
|
|
6465
|
-
});
|
|
6850
|
+
path,
|
|
6851
|
+
file_types,
|
|
6852
|
+
top_k,
|
|
6853
|
+
threshold,
|
|
6854
|
+
max_files
|
|
6855
|
+
}));
|
|
6466
6856
|
} catch (error) {
|
|
6467
6857
|
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
6468
6858
|
}
|
|
@@ -6610,7 +7000,7 @@ server.tool(
|
|
|
6610
7000
|
{
|
|
6611
7001
|
action: z.enum(["source", "workspace"]).default("source"),
|
|
6612
7002
|
project: z.string().optional(),
|
|
6613
|
-
type: z.enum(["github", "web", "pdf", "local", "slack", "video"]).optional(),
|
|
7003
|
+
type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional(),
|
|
6614
7004
|
name: z.string().optional(),
|
|
6615
7005
|
owner: z.string().optional(),
|
|
6616
7006
|
repo: z.string().optional(),
|
|
@@ -6766,25 +7156,55 @@ server.tool(
|
|
|
6766
7156
|
);
|
|
6767
7157
|
server.tool(
|
|
6768
7158
|
"learn",
|
|
6769
|
-
"
|
|
7159
|
+
"Unified learning tool for conversation memory, text ingestion, and source indexing. Prefer this over the older learning-adjacent compatibility tools.",
|
|
6770
7160
|
{
|
|
7161
|
+
mode: z.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
|
|
6771
7162
|
project: z.string().optional(),
|
|
7163
|
+
user_id: z.string().optional().describe("Optional end-user identity for conversation learning"),
|
|
7164
|
+
session_id: z.string().optional().describe("Session identifier for conversation learning"),
|
|
7165
|
+
messages: z.array(z.object({
|
|
7166
|
+
role: z.string(),
|
|
7167
|
+
content: z.string(),
|
|
7168
|
+
timestamp: z.string().optional()
|
|
7169
|
+
})).optional().describe("Conversation messages to learn from"),
|
|
7170
|
+
title: z.string().optional().describe("Title for text learning"),
|
|
6772
7171
|
content: z.string().optional().describe("Inline text content to ingest"),
|
|
6773
|
-
|
|
7172
|
+
metadata: z.record(z.any()).optional(),
|
|
7173
|
+
tags: z.array(z.string()).optional(),
|
|
7174
|
+
namespace: z.string().optional(),
|
|
7175
|
+
type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional().describe("Source type when mode=source"),
|
|
6774
7176
|
url: z.string().optional().describe("URL to learn from"),
|
|
6775
7177
|
owner: z.string().optional().describe("GitHub owner"),
|
|
6776
7178
|
repo: z.string().optional().describe("GitHub repository"),
|
|
6777
7179
|
branch: z.string().optional(),
|
|
7180
|
+
paths: z.array(z.string()).optional(),
|
|
6778
7181
|
path: z.string().optional().describe("Local path to learn from"),
|
|
6779
7182
|
file_path: z.string().optional().describe("Single file path to learn from"),
|
|
6780
7183
|
name: z.string().optional().describe("Optional source name"),
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
7184
|
+
channel_ids: z.array(z.string()).optional(),
|
|
7185
|
+
since: z.string().optional(),
|
|
7186
|
+
token: z.string().optional(),
|
|
7187
|
+
auth_ref: z.string().optional(),
|
|
7188
|
+
platform: z.enum(["youtube", "loom", "generic"]).optional(),
|
|
7189
|
+
language: z.string().optional(),
|
|
7190
|
+
options: z.object({
|
|
7191
|
+
async: z.boolean().optional(),
|
|
7192
|
+
auto_index: z.boolean().optional(),
|
|
7193
|
+
ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
|
|
7194
|
+
strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
|
|
7195
|
+
profile_config: z.record(z.any()).optional(),
|
|
7196
|
+
crawl_depth: z.number().optional(),
|
|
7197
|
+
include_paths: z.array(z.string()).optional(),
|
|
7198
|
+
exclude_paths: z.array(z.string()).optional(),
|
|
7199
|
+
glob: z.string().optional(),
|
|
7200
|
+
max_files: z.number().optional(),
|
|
7201
|
+
max_pages: z.number().optional(),
|
|
7202
|
+
extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
|
|
7203
|
+
workspace_id: z.string().optional(),
|
|
7204
|
+
allow_stt_fallback: z.boolean().optional(),
|
|
7205
|
+
max_duration_minutes: z.number().optional(),
|
|
7206
|
+
max_chunks: z.number().optional()
|
|
7207
|
+
}).optional()
|
|
6788
7208
|
},
|
|
6789
7209
|
async (input) => {
|
|
6790
7210
|
try {
|
|
@@ -6867,8 +7287,14 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
|
6867
7287
|
main().catch(console.error);
|
|
6868
7288
|
}
|
|
6869
7289
|
export {
|
|
7290
|
+
canonicalizeWorkspacePath,
|
|
7291
|
+
chooseWorkspaceProjectSource,
|
|
7292
|
+
classifyWorkspaceHealth,
|
|
6870
7293
|
createMcpServer,
|
|
6871
7294
|
createWhisperMcpClient,
|
|
6872
7295
|
createWhisperMcpRuntimeClient,
|
|
6873
|
-
|
|
7296
|
+
extractSignature,
|
|
7297
|
+
renderScopedMcpConfig,
|
|
7298
|
+
resolveForgetQueryCandidates,
|
|
7299
|
+
runSearchCodeSearch
|
|
6874
7300
|
};
|