|
3 | 3 | */ |
4 | 4 |
|
5 | 5 | import type { |
| 6 | + DeleteSearchViewArgs, |
6 | 7 | ListSearchViewsArgs, |
7 | 8 | LoadMoreContextArgs, |
8 | 9 | LoadTurnContextArgs, |
@@ -572,6 +573,50 @@ export function createContextTools(ctx: Ctx): ToolDefinition[] { |
572 | 573 | }); |
573 | 574 | } |
574 | 575 |
|
| 576 | + async function handleDeleteSearchView(args: DeleteSearchViewArgs) { |
| 577 | + const key = typeof args.key === 'string' ? args.key.trim() : ''; |
| 578 | + if (!key) { |
| 579 | + return toolResult({ success: false, error: 'Missing or invalid "key"' }, true); |
| 580 | + } |
| 581 | + const deleteRefinedTurns = args.deleteRefinedTurns === true; |
| 582 | + const filePath = path.join(storeRoot, 'searches', `${key}.json`); |
| 583 | + if (!fs.existsSync(filePath)) { |
| 584 | + return toolResult({ success: false, error: 'Search view not found' }, true); |
| 585 | + } |
| 586 | + |
| 587 | + const refinedTurnsToDelete: Array<{ sessionId: string; turnId: number }> = []; |
| 588 | + if (deleteRefinedTurns) { |
| 589 | + try { |
| 590 | + const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); |
| 591 | + const clusters = Array.isArray(data.clusters) ? data.clusters : []; |
| 592 | + const seen = new Set<string>(); |
| 593 | + for (const cluster of clusters) { |
| 594 | + const members = Array.isArray(cluster.members) ? cluster.members : []; |
| 595 | + for (const m of members) { |
| 596 | + if (m && typeof m.sessionId === 'string' && typeof m.turnId === 'number') { |
| 597 | + const id = `${m.sessionId}:${m.turnId}`; |
| 598 | + if (!seen.has(id)) { |
| 599 | + seen.add(id); |
| 600 | + refinedTurnsToDelete.push({ sessionId: m.sessionId, turnId: m.turnId }); |
| 601 | + } |
| 602 | + } |
| 603 | + } |
| 604 | + } |
| 605 | + } catch { |
| 606 | + // Ignore parse errors and proceed with deleting the view file. |
| 607 | + } |
| 608 | + } |
| 609 | + |
| 610 | + fs.unlinkSync(filePath); |
| 611 | + |
| 612 | + let deletedRefinedTurns = 0; |
| 613 | + if (deleteRefinedTurns && refinedTurnsToDelete.length > 0) { |
| 614 | + deletedRefinedTurns = await refinedManager.deleteRefinedTurns(refinedTurnsToDelete); |
| 615 | + } |
| 616 | + |
| 617 | + return toolResult({ success: true, deletedRefinedTurns }); |
| 618 | + } |
| 619 | + |
575 | 620 | const tools: ToolDefinition[] = [ |
576 | 621 | { |
577 | 622 | name: 'load_workspace_context', |
@@ -661,6 +706,23 @@ export function createContextTools(ctx: Ctx): ToolDefinition[] { |
661 | 706 | }, |
662 | 707 | handler: adaptHandler(handleListSearchViews), |
663 | 708 | }, |
| 709 | + { |
| 710 | + name: 'delete_search_view', |
| 711 | + description: |
| 712 | + 'Delete a saved search view. Set deleteRefinedTurns to true to also remove the refined turns referenced by this view (useful for purging low-quality refined data).', |
| 713 | + inputSchema: { |
| 714 | + type: 'object', |
| 715 | + properties: { |
| 716 | + key: { type: 'string', description: 'Search view file key (e.g. search-abc123.json without extension)' }, |
| 717 | + deleteRefinedTurns: { |
| 718 | + type: 'boolean', |
| 719 | + description: 'If true, also delete all refined turns referenced by this view.', |
| 720 | + }, |
| 721 | + }, |
| 722 | + required: ['key'], |
| 723 | + }, |
| 724 | + handler: adaptHandler(handleDeleteSearchView), |
| 725 | + }, |
664 | 726 | { |
665 | 727 | name: 'load_turn_context', |
666 | 728 | description: |
|
0 commit comments