Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion multimodal/tarko/agent-server/src/api/controllers/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export async function createSession(req: Request, res: Response) {
server.storageUnsubscribes[sessionId] = storageUnsubscribe;
}


// Wait a short time to ensure all initialization events are persisted
// This handles the async nature of event storage during agent initialization
await session.waitForEventSavesToComplete();
Expand Down Expand Up @@ -633,8 +632,26 @@ async function searchWorkspaceItemsRecursive(

await searchInDirectory(basePath);

// Keep dist searchable, but avoid generated artifacts outranking source matches.
const sourceItems = new Set(
items.filter((item) => hasPathSegment(item.relativePath, SOURCE_DIRECTORY)),
);
const shouldPrioritizeSourceItems =
!hasPathSegment(query, GENERATED_OUTPUT_DIRECTORY) &&
sourceItems.size > 0 &&
items.some((item) => hasPathSegment(item.relativePath, GENERATED_OUTPUT_DIRECTORY));

// Smart relevance-based sorting
return items.sort((a, b) => {
if (shouldPrioritizeSourceItems) {
const sourceA = sourceItems.has(a);
const sourceB = sourceItems.has(b);

if (sourceA !== sourceB) {
return sourceA ? -1 : 1;
}
}

const scoreA = calculateRelevanceScore(a, query);
const scoreB = calculateRelevanceScore(b, query);

Expand All @@ -653,6 +670,13 @@ async function searchWorkspaceItemsRecursive(
});
}

const SOURCE_DIRECTORY = 'src';
const GENERATED_OUTPUT_DIRECTORY = 'dist';

function hasPathSegment(value: string, segment: string): boolean {
return value.toLowerCase().replace(/\\/g, '/').split('/').filter(Boolean).includes(segment);
}

/**
* Calculate relevance score for search results
* Higher score means more relevant to the query
Expand Down
96 changes: 96 additions & 0 deletions multimodal/tarko/agent-server/tests/api/workspace-search.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
* SPDX-License-Identifier: Apache-2.0
*/

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import type { Request, Response } from 'express';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

vi.unmock('fs');
vi.unmock('path');

import { searchWorkspaceItems } from '../../src/api/controllers/sessions';

describe('workspace search ordering', () => {
let workspacePath: string;

beforeEach(async () => {
workspacePath = await fs.mkdtemp(path.join(os.tmpdir(), 'tarko-workspace-search-'));

await Promise.all([
fs.mkdir(path.join(workspacePath, 'dist'), { recursive: true }),
fs.mkdir(path.join(workspacePath, 'src', 'runtime'), { recursive: true }),
]);

await Promise.all([
fs.writeFile(path.join(workspacePath, 'dist', 'repository-context.d.ts'), ''),
fs.writeFile(path.join(workspacePath, 'dist', 'repository-context.d.ts.map'), ''),
fs.writeFile(path.join(workspacePath, 'dist', 'repository-context.js'), ''),
fs.writeFile(path.join(workspacePath, 'dist', 'repository-context.js.map'), ''),
fs.writeFile(path.join(workspacePath, 'dist', 'repository-context.mjs'), ''),
...Array.from({ length: 25 }, (_, index) =>
fs.writeFile(path.join(workspacePath, 'dist', `repository-context-${index}.js`), ''),
),
fs.writeFile(path.join(workspacePath, 'src', 'runtime', 'repository-context.ts'), ''),
]);
});

afterEach(async () => {
await fs.rm(workspacePath, { recursive: true, force: true });
});

async function search(query: string) {
const json = vi.fn();
const req = {
query: {
sessionId: 'test-session',
q: query,
type: 'file',
},
app: {
locals: {
server: {
getCurrentWorkspace: () => workspacePath,
},
},
},
} as unknown as Request;
const res = {
status: vi.fn().mockReturnThis(),
json,
} as unknown as Response;

await searchWorkspaceItems(req, res);

expect(json).toHaveBeenCalledOnce();
return json.mock.calls[0][0].items as Array<{ relativePath: string }>;
}

it('ranks source files ahead of matching dist artifacts', async () => {
const results = await search('context');
const paths = results.map((item) => item.relativePath);

expect(paths).toHaveLength(20);
expect(paths[0]).toBe('src/runtime/repository-context.ts');
expect(paths.slice(1).every((resultPath) => resultPath.startsWith('dist/'))).toBe(true);
});

it('keeps dist artifacts searchable when the query names dist explicitly', async () => {
const results = await search('dist');

expect(results).not.toHaveLength(0);
expect(results.every((item) => item.relativePath.startsWith('dist/'))).toBe(true);
});

it('preserves relevance ordering when there is no source match', async () => {
await fs.mkdir(path.join(workspacePath, 'notes'));
await fs.writeFile(path.join(workspacePath, 'notes', 'repository-context-0.js.notes.md'), '');

const results = await search('repository-context-0.js');

expect(results[0].relativePath).toBe('dist/repository-context-0.js');
});
});