iosm-cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +6 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +1 -0
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +3 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +11 -19
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/semantic/chunking.d.ts +10 -0
- package/dist/core/semantic/chunking.d.ts.map +1 -0
- package/dist/core/semantic/chunking.js +82 -0
- package/dist/core/semantic/chunking.js.map +1 -0
- package/dist/core/semantic/cli.d.ts +23 -0
- package/dist/core/semantic/cli.d.ts.map +1 -0
- package/dist/core/semantic/cli.js +86 -0
- package/dist/core/semantic/cli.js.map +1 -0
- package/dist/core/semantic/config.d.ts +8 -0
- package/dist/core/semantic/config.d.ts.map +1 -0
- package/dist/core/semantic/config.js +261 -0
- package/dist/core/semantic/config.js.map +1 -0
- package/dist/core/semantic/index-store.d.ts +21 -0
- package/dist/core/semantic/index-store.d.ts.map +1 -0
- package/dist/core/semantic/index-store.js +73 -0
- package/dist/core/semantic/index-store.js.map +1 -0
- package/dist/core/semantic/index.d.ts +8 -0
- package/dist/core/semantic/index.d.ts.map +1 -0
- package/dist/core/semantic/index.js +7 -0
- package/dist/core/semantic/index.js.map +1 -0
- package/dist/core/semantic/providers.d.ts +22 -0
- package/dist/core/semantic/providers.d.ts.map +1 -0
- package/dist/core/semantic/providers.js +317 -0
- package/dist/core/semantic/providers.js.map +1 -0
- package/dist/core/semantic/runtime.d.ts +32 -0
- package/dist/core/semantic/runtime.d.ts.map +1 -0
- package/dist/core/semantic/runtime.js +499 -0
- package/dist/core/semantic/runtime.js.map +1 -0
- package/dist/core/semantic/types.d.ts +151 -0
- package/dist/core/semantic/types.d.ts.map +1 -0
- package/dist/core/semantic/types.js +17 -0
- package/dist/core/semantic/types.js.map +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +19 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ast-grep.js +1 -1
- package/dist/core/tools/ast-grep.js.map +1 -1
- package/dist/core/tools/comby.js +1 -1
- package/dist/core/tools/comby.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +6 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/rg.js +1 -1
- package/dist/core/tools/rg.js.map +1 -1
- package/dist/core/tools/semantic-search.d.ts +21 -0
- package/dist/core/tools/semantic-search.d.ts.map +1 -0
- package/dist/core/tools/semantic-search.js +122 -0
- package/dist/core/tools/semantic-search.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +117 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +15 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +687 -4
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +18 -1
- package/docs/configuration.md +74 -2
- package/docs/getting-started.md +1 -0
- package/docs/interactive-mode.md +5 -1
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ import { ModelRegistry } from "../../core/model-registry.js";
|
|
|
19
19
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
20
20
|
import { getMcpCommandHelp, parseMcpAddCommand, parseMcpTargetCommand, } from "../../core/mcp/index.js";
|
|
21
21
|
import { addMemoryEntry, getMemoryFilePath, readMemoryEntries, removeMemoryEntry, updateMemoryEntry, } from "../../core/memory.js";
|
|
22
|
+
import { getDefaultSemanticSearchConfig, getSemanticConfigPath, getSemanticIndexDir, isLikelyEmbeddingModelId, listOllamaLocalModels, listOpenRouterEmbeddingModels, loadMergedSemanticConfig, SemanticConfigMissingError, SemanticRebuildRequiredError, SemanticSearchRuntime, upsertScopedSemanticSearchConfig, } from "../../core/semantic/index.js";
|
|
22
23
|
import { DefaultResourceLoader } from "../../core/resource-loader.js";
|
|
23
24
|
import { createAgentSession } from "../../core/sdk.js";
|
|
24
25
|
import { createTeamRun, getTeamRun, listTeamRuns } from "../../core/agent-teams.js";
|
|
@@ -797,6 +798,42 @@ export class InteractiveMode {
|
|
|
797
798
|
}
|
|
798
799
|
return null;
|
|
799
800
|
}
|
|
801
|
+
getSemanticArgumentCompletions(prefix) {
|
|
802
|
+
const subcommands = ["ui", "setup", "status", "index", "rebuild", "query", "help"];
|
|
803
|
+
const queryFlags = ["--top-k"];
|
|
804
|
+
const topKValues = ["5", "8", "10", "20"];
|
|
805
|
+
const hasTrailingSpace = /\\s$/.test(prefix);
|
|
806
|
+
const tokens = this.parseSlashArgs(prefix);
|
|
807
|
+
const first = tokens[0]?.toLowerCase();
|
|
808
|
+
if (!first || (tokens.length === 1 && !hasTrailingSpace)) {
|
|
809
|
+
const query = first ?? "";
|
|
810
|
+
const candidates = subcommands.filter((item) => item.includes(query));
|
|
811
|
+
return candidates.map((item) => ({ value: item, label: item }));
|
|
812
|
+
}
|
|
813
|
+
if (first !== "query") {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
const topKIndex = tokens.findIndex((token) => token === "--top-k");
|
|
817
|
+
if (topKIndex >= 0) {
|
|
818
|
+
const currentValue = tokens[topKIndex + 1];
|
|
819
|
+
if (!currentValue) {
|
|
820
|
+
return topKValues.map((value) => ({ value, label: value }));
|
|
821
|
+
}
|
|
822
|
+
return topKValues
|
|
823
|
+
.filter((value) => value.startsWith(currentValue))
|
|
824
|
+
.map((value) => ({ value, label: value }));
|
|
825
|
+
}
|
|
826
|
+
const query = hasTrailingSpace ? "" : (tokens[tokens.length - 1] ?? "");
|
|
827
|
+
if (query.startsWith("--")) {
|
|
828
|
+
return queryFlags
|
|
829
|
+
.filter((flag) => flag.includes(query))
|
|
830
|
+
.map((flag) => ({ value: flag, label: flag }));
|
|
831
|
+
}
|
|
832
|
+
if (hasTrailingSpace) {
|
|
833
|
+
return queryFlags.map((flag) => ({ value: flag, label: flag }));
|
|
834
|
+
}
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
800
837
|
setupAutocomplete(fdPath) {
|
|
801
838
|
// Define commands for autocomplete
|
|
802
839
|
const builtinCommands = BUILTIN_SLASH_COMMANDS.filter((command) => this.activeProfileName === "iosm" || !IOSM_PROFILE_ONLY_COMMANDS.has(command.name));
|
|
@@ -838,6 +875,10 @@ export class InteractiveMode {
|
|
|
838
875
|
if (memoryCommand) {
|
|
839
876
|
memoryCommand.getArgumentCompletions = (prefix) => this.getMemoryArgumentCompletions(prefix);
|
|
840
877
|
}
|
|
878
|
+
const semanticCommand = slashCommands.find((command) => command.name === "semantic");
|
|
879
|
+
if (semanticCommand) {
|
|
880
|
+
semanticCommand.getArgumentCompletions = (prefix) => this.getSemanticArgumentCompletions(prefix);
|
|
881
|
+
}
|
|
841
882
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
842
883
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
843
884
|
name: cmd.name,
|
|
@@ -1091,7 +1132,9 @@ export class InteractiveMode {
|
|
|
1091
1132
|
}
|
|
1092
1133
|
// Commands and keys
|
|
1093
1134
|
const commandsLine = `${pad}${theme.fg("dim", "cmds")} ` +
|
|
1094
|
-
["model", "login", "mcp", "memory", "doctor", "new"]
|
|
1135
|
+
["model", "login", "mcp", "semantic", "memory", "doctor", "new"]
|
|
1136
|
+
.map((c) => theme.fg("accent", `/${c}`))
|
|
1137
|
+
.join(theme.fg("dim", " "));
|
|
1095
1138
|
const keysLine = `${pad}${theme.fg("dim", "keys")} ` +
|
|
1096
1139
|
appKeyHint(kb, "interrupt", "stop") +
|
|
1097
1140
|
theme.fg("dim", " ") +
|
|
@@ -1985,6 +2028,28 @@ export class InteractiveMode {
|
|
|
1985
2028
|
const result = await this.showExtensionSelector(`${title}\n${message}`, ["Yes", "No"], opts);
|
|
1986
2029
|
return result === "Yes";
|
|
1987
2030
|
}
|
|
2031
|
+
async runWithExtensionLoader(message, task) {
|
|
2032
|
+
const loader = new BorderedLoader(this.ui, theme, message, {
|
|
2033
|
+
cancellable: false,
|
|
2034
|
+
});
|
|
2035
|
+
this.editorContainer.clear();
|
|
2036
|
+
this.editorContainer.addChild(loader);
|
|
2037
|
+
this.ui.setFocus(loader);
|
|
2038
|
+
this.ui.requestRender();
|
|
2039
|
+
const restoreEditor = () => {
|
|
2040
|
+
loader.dispose();
|
|
2041
|
+
this.editorContainer.clear();
|
|
2042
|
+
this.editorContainer.addChild(this.editor);
|
|
2043
|
+
this.ui.setFocus(this.editor);
|
|
2044
|
+
this.ui.requestRender();
|
|
2045
|
+
};
|
|
2046
|
+
try {
|
|
2047
|
+
return await task();
|
|
2048
|
+
}
|
|
2049
|
+
finally {
|
|
2050
|
+
restoreEditor();
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
1988
2053
|
/**
|
|
1989
2054
|
* Show a text input for extensions.
|
|
1990
2055
|
*/
|
|
@@ -2384,6 +2449,11 @@ export class InteractiveMode {
|
|
|
2384
2449
|
await this.handleMemoryCommand(text);
|
|
2385
2450
|
return;
|
|
2386
2451
|
}
|
|
2452
|
+
if (text === "/semantic" || text.startsWith("/semantic ")) {
|
|
2453
|
+
this.editor.setText("");
|
|
2454
|
+
await this.handleSemanticCommand(text);
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2387
2457
|
if (text === "/settings") {
|
|
2388
2458
|
this.showSettingsSelector();
|
|
2389
2459
|
this.editor.setText("");
|
|
@@ -5018,7 +5088,8 @@ export class InteractiveMode {
|
|
|
5018
5088
|
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
|
5019
5089
|
return providerInfo?.name || providerId;
|
|
5020
5090
|
}
|
|
5021
|
-
async handleOpenRouterApiKeyLogin() {
|
|
5091
|
+
async handleOpenRouterApiKeyLogin(options) {
|
|
5092
|
+
const openModelSelector = options?.openModelSelector ?? true;
|
|
5022
5093
|
const providerName = this.getProviderDisplayName(OPENROUTER_PROVIDER_ID);
|
|
5023
5094
|
const existingCredential = this.session.modelRegistry.authStorage.get(OPENROUTER_PROVIDER_ID);
|
|
5024
5095
|
if (existingCredential) {
|
|
@@ -5041,7 +5112,9 @@ export class InteractiveMode {
|
|
|
5041
5112
|
this.session.modelRegistry.authStorage.set(OPENROUTER_PROVIDER_ID, { type: "api_key", key: apiKey });
|
|
5042
5113
|
await this.updateAvailableProviderCount();
|
|
5043
5114
|
this.showStatus(`${providerName} API key saved to ${getAuthPath()}`);
|
|
5044
|
-
|
|
5115
|
+
if (openModelSelector) {
|
|
5116
|
+
await this.showModelProviderSelector(OPENROUTER_PROVIDER_ID);
|
|
5117
|
+
}
|
|
5045
5118
|
}
|
|
5046
5119
|
async showLoginDialog(providerId) {
|
|
5047
5120
|
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
|
@@ -5499,6 +5572,567 @@ export class InteractiveMode {
|
|
|
5499
5572
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
5500
5573
|
}
|
|
5501
5574
|
}
|
|
5575
|
+
createSemanticRuntime() {
|
|
5576
|
+
return new SemanticSearchRuntime({
|
|
5577
|
+
cwd: this.sessionManager.getCwd(),
|
|
5578
|
+
agentDir: getAgentDir(),
|
|
5579
|
+
authStorage: this.session.modelRegistry.authStorage,
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
parseSemanticScopeOptions(args) {
|
|
5583
|
+
let scope;
|
|
5584
|
+
const rest = [];
|
|
5585
|
+
for (let index = 0; index < args.length; index++) {
|
|
5586
|
+
const token = args[index] ?? "";
|
|
5587
|
+
const normalized = token.toLowerCase();
|
|
5588
|
+
if (normalized === "--scope") {
|
|
5589
|
+
const value = (args[index + 1] ?? "").toLowerCase();
|
|
5590
|
+
if (!value) {
|
|
5591
|
+
return { scope, rest, error: "Usage: /semantic setup --scope <user|project>" };
|
|
5592
|
+
}
|
|
5593
|
+
if (value !== "user" && value !== "project") {
|
|
5594
|
+
return { scope, rest, error: `Invalid semantic scope "${value}". Use user or project.` };
|
|
5595
|
+
}
|
|
5596
|
+
scope = value;
|
|
5597
|
+
index += 1;
|
|
5598
|
+
continue;
|
|
5599
|
+
}
|
|
5600
|
+
rest.push(token);
|
|
5601
|
+
}
|
|
5602
|
+
return { scope, rest };
|
|
5603
|
+
}
|
|
5604
|
+
parseSemanticTopKOptions(args) {
|
|
5605
|
+
let topK;
|
|
5606
|
+
const rest = [];
|
|
5607
|
+
for (let index = 0; index < args.length; index++) {
|
|
5608
|
+
const token = args[index] ?? "";
|
|
5609
|
+
const normalized = token.toLowerCase();
|
|
5610
|
+
if (normalized === "--top-k" || normalized === "--topk") {
|
|
5611
|
+
const raw = (args[index + 1] ?? "").trim();
|
|
5612
|
+
if (!raw) {
|
|
5613
|
+
return { topK, rest, error: "Usage: /semantic query <text> [--top-k 1..20]" };
|
|
5614
|
+
}
|
|
5615
|
+
const parsed = Number.parseInt(raw, 10);
|
|
5616
|
+
if (!Number.isFinite(parsed) || `${parsed}` !== raw || parsed < 1 || parsed > 20) {
|
|
5617
|
+
return { topK, rest, error: "--top-k must be an integer between 1 and 20." };
|
|
5618
|
+
}
|
|
5619
|
+
topK = parsed;
|
|
5620
|
+
index += 1;
|
|
5621
|
+
continue;
|
|
5622
|
+
}
|
|
5623
|
+
rest.push(token);
|
|
5624
|
+
}
|
|
5625
|
+
return { topK, rest };
|
|
5626
|
+
}
|
|
5627
|
+
formatSemanticStatusReport(status) {
|
|
5628
|
+
const lines = [
|
|
5629
|
+
`configured: ${status.configured ? "yes" : "no"}`,
|
|
5630
|
+
`enabled: ${status.enabled ? "yes" : "no"}`,
|
|
5631
|
+
`indexed: ${status.indexed ? "yes" : "no"}`,
|
|
5632
|
+
`stale: ${status.stale ? `yes${status.staleReason ? ` (${status.staleReason})` : ""}` : "no"}`,
|
|
5633
|
+
];
|
|
5634
|
+
if (status.provider)
|
|
5635
|
+
lines.push(`provider: ${status.provider}`);
|
|
5636
|
+
if (status.model)
|
|
5637
|
+
lines.push(`model: ${status.model}`);
|
|
5638
|
+
lines.push(`files: ${status.files}`);
|
|
5639
|
+
lines.push(`chunks: ${status.chunks}`);
|
|
5640
|
+
if (status.dimension !== undefined)
|
|
5641
|
+
lines.push(`dimension: ${status.dimension}`);
|
|
5642
|
+
if (status.ageSeconds !== undefined)
|
|
5643
|
+
lines.push(`age_seconds: ${status.ageSeconds}`);
|
|
5644
|
+
lines.push(`index_path: ${status.indexPath}`);
|
|
5645
|
+
lines.push(`config_user: ${status.configPathUser}`);
|
|
5646
|
+
lines.push(`config_project: ${status.configPathProject}`);
|
|
5647
|
+
if (!status.configured) {
|
|
5648
|
+
lines.push("hint: run /semantic setup");
|
|
5649
|
+
}
|
|
5650
|
+
return lines.join("\n");
|
|
5651
|
+
}
|
|
5652
|
+
formatSemanticIndexReport(result) {
|
|
5653
|
+
return [
|
|
5654
|
+
`action: ${result.action}`,
|
|
5655
|
+
`processed_files: ${result.processedFiles}`,
|
|
5656
|
+
`reused_files: ${result.reusedFiles}`,
|
|
5657
|
+
`new_files: ${result.newFiles}`,
|
|
5658
|
+
`changed_files: ${result.changedFiles}`,
|
|
5659
|
+
`removed_files: ${result.removedFiles}`,
|
|
5660
|
+
`chunks: ${result.chunks}`,
|
|
5661
|
+
`dimension: ${result.dimension}`,
|
|
5662
|
+
`duration_ms: ${result.durationMs}`,
|
|
5663
|
+
`built_at: ${result.builtAt}`,
|
|
5664
|
+
`index_path: ${result.indexPath}`,
|
|
5665
|
+
].join("\n");
|
|
5666
|
+
}
|
|
5667
|
+
formatSemanticQueryReport(result) {
|
|
5668
|
+
const lines = [
|
|
5669
|
+
`query: ${result.query}`,
|
|
5670
|
+
`top_k: ${result.topK}`,
|
|
5671
|
+
`auto_refreshed: ${result.autoRefreshed ? "yes" : "no"}`,
|
|
5672
|
+
];
|
|
5673
|
+
if (result.hits.length === 0) {
|
|
5674
|
+
lines.push("hits: none");
|
|
5675
|
+
return lines.join("\n");
|
|
5676
|
+
}
|
|
5677
|
+
lines.push("hits:");
|
|
5678
|
+
for (let index = 0; index < result.hits.length; index++) {
|
|
5679
|
+
const hit = result.hits[index];
|
|
5680
|
+
lines.push(`${index + 1}. score=${hit.score.toFixed(4)} ${hit.path}:${hit.lineStart}-${hit.lineEnd}`);
|
|
5681
|
+
lines.push(` ${hit.snippet}`);
|
|
5682
|
+
}
|
|
5683
|
+
return lines.join("\n");
|
|
5684
|
+
}
|
|
5685
|
+
getSemanticSetupProviderLabel(type) {
|
|
5686
|
+
if (type === "openrouter")
|
|
5687
|
+
return "openrouter";
|
|
5688
|
+
if (type === "ollama")
|
|
5689
|
+
return "ollama";
|
|
5690
|
+
return "custom_openai";
|
|
5691
|
+
}
|
|
5692
|
+
async ensureOpenRouterSemanticCredentials() {
|
|
5693
|
+
const existing = await this.session.modelRegistry.authStorage.getApiKey(OPENROUTER_PROVIDER_ID);
|
|
5694
|
+
if (existing)
|
|
5695
|
+
return;
|
|
5696
|
+
const shouldLogin = await this.showExtensionConfirm("Semantic setup: OpenRouter key missing", "No OpenRouter API key found. Open login flow now?");
|
|
5697
|
+
if (!shouldLogin) {
|
|
5698
|
+
this.showWarning("OpenRouter key is missing. semantic index/query will fail until credentials are added.");
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
await this.handleOpenRouterApiKeyLogin({ openModelSelector: false });
|
|
5702
|
+
const afterLogin = await this.session.modelRegistry.authStorage.getApiKey(OPENROUTER_PROVIDER_ID);
|
|
5703
|
+
if (!afterLogin) {
|
|
5704
|
+
this.showWarning("OpenRouter key is still missing. You can run /login later.");
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
async selectSemanticModelFromCatalog(title, catalogModels, currentModel, options) {
|
|
5708
|
+
const normalizedCurrent = currentModel.trim();
|
|
5709
|
+
const uniqueModels = [];
|
|
5710
|
+
const seen = new Set();
|
|
5711
|
+
for (const model of catalogModels) {
|
|
5712
|
+
const normalized = model.trim();
|
|
5713
|
+
if (!normalized || seen.has(normalized))
|
|
5714
|
+
continue;
|
|
5715
|
+
seen.add(normalized);
|
|
5716
|
+
uniqueModels.push(normalized);
|
|
5717
|
+
}
|
|
5718
|
+
const optionToModel = new Map();
|
|
5719
|
+
const selectorOptions = [];
|
|
5720
|
+
const addOption = (label, modelId) => {
|
|
5721
|
+
let uniqueLabel = label;
|
|
5722
|
+
let suffix = 2;
|
|
5723
|
+
while (optionToModel.has(uniqueLabel)) {
|
|
5724
|
+
uniqueLabel = `${label} (${suffix})`;
|
|
5725
|
+
suffix += 1;
|
|
5726
|
+
}
|
|
5727
|
+
optionToModel.set(uniqueLabel, modelId);
|
|
5728
|
+
selectorOptions.push(uniqueLabel);
|
|
5729
|
+
};
|
|
5730
|
+
if (normalizedCurrent) {
|
|
5731
|
+
addOption(`Current: ${normalizedCurrent}`, normalizedCurrent);
|
|
5732
|
+
}
|
|
5733
|
+
for (const modelId of uniqueModels) {
|
|
5734
|
+
const marker = options?.highlightLikelyEmbedding && isLikelyEmbeddingModelId(modelId) ? " [embedding]" : "";
|
|
5735
|
+
addOption(`${modelId}${marker}`, modelId);
|
|
5736
|
+
}
|
|
5737
|
+
const manualOption = "Enter model manually";
|
|
5738
|
+
selectorOptions.push(manualOption);
|
|
5739
|
+
const selected = await this.showExtensionSelector(title, selectorOptions);
|
|
5740
|
+
if (!selected)
|
|
5741
|
+
return undefined;
|
|
5742
|
+
if (selected === manualOption) {
|
|
5743
|
+
const modelInput = await this.showExtensionInput(`${title}: model`, normalizedCurrent || "model-id");
|
|
5744
|
+
if (modelInput === undefined)
|
|
5745
|
+
return undefined;
|
|
5746
|
+
const model = modelInput.trim();
|
|
5747
|
+
if (!model) {
|
|
5748
|
+
this.showWarning("Model cannot be empty.");
|
|
5749
|
+
return undefined;
|
|
5750
|
+
}
|
|
5751
|
+
return model;
|
|
5752
|
+
}
|
|
5753
|
+
return optionToModel.get(selected);
|
|
5754
|
+
}
|
|
5755
|
+
async runSemanticSetupWizard(initialScope) {
|
|
5756
|
+
const cwd = this.sessionManager.getCwd();
|
|
5757
|
+
const agentDir = getAgentDir();
|
|
5758
|
+
const merged = loadMergedSemanticConfig(cwd, agentDir);
|
|
5759
|
+
const config = merged.config ?? getDefaultSemanticSearchConfig();
|
|
5760
|
+
let scope = initialScope;
|
|
5761
|
+
if (!scope) {
|
|
5762
|
+
const pickedScope = await this.showExtensionSelector("/semantic setup: scope", [
|
|
5763
|
+
"user (Recommended)",
|
|
5764
|
+
"project",
|
|
5765
|
+
]);
|
|
5766
|
+
if (!pickedScope) {
|
|
5767
|
+
this.showStatus("Semantic setup cancelled");
|
|
5768
|
+
return;
|
|
5769
|
+
}
|
|
5770
|
+
scope = pickedScope.startsWith("project") ? "project" : "user";
|
|
5771
|
+
}
|
|
5772
|
+
const providerOption = await this.showExtensionSelector("/semantic setup: provider", [
|
|
5773
|
+
"openrouter (Recommended)",
|
|
5774
|
+
"ollama",
|
|
5775
|
+
"custom_openai",
|
|
5776
|
+
]);
|
|
5777
|
+
if (!providerOption) {
|
|
5778
|
+
this.showStatus("Semantic setup cancelled");
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
const providerType = providerOption.startsWith("ollama")
|
|
5782
|
+
? "ollama"
|
|
5783
|
+
: providerOption.startsWith("custom_openai")
|
|
5784
|
+
? "custom_openai"
|
|
5785
|
+
: "openrouter";
|
|
5786
|
+
const providerDefaults = {
|
|
5787
|
+
openrouter: "openai/text-embedding-3-small",
|
|
5788
|
+
ollama: "nomic-embed-text",
|
|
5789
|
+
custom_openai: "text-embedding-3-small",
|
|
5790
|
+
};
|
|
5791
|
+
const currentModel = (config.provider.type === providerType ? config.provider.model : providerDefaults[providerType]).trim();
|
|
5792
|
+
let model = currentModel;
|
|
5793
|
+
const nextProvider = {
|
|
5794
|
+
...config.provider,
|
|
5795
|
+
type: providerType,
|
|
5796
|
+
model: model || providerDefaults[providerType],
|
|
5797
|
+
};
|
|
5798
|
+
if (providerType === "ollama") {
|
|
5799
|
+
const defaultBase = (config.provider.type === "ollama" ? config.provider.baseUrl : undefined) ?? "http://127.0.0.1:11434";
|
|
5800
|
+
const baseUrlInput = await this.showExtensionInput("/semantic setup: ollama base URL", defaultBase);
|
|
5801
|
+
if (baseUrlInput === undefined) {
|
|
5802
|
+
this.showStatus("Semantic setup cancelled");
|
|
5803
|
+
return;
|
|
5804
|
+
}
|
|
5805
|
+
const baseUrl = baseUrlInput.trim();
|
|
5806
|
+
nextProvider.baseUrl = baseUrl || undefined;
|
|
5807
|
+
nextProvider.apiKeyEnv = undefined;
|
|
5808
|
+
let ollamaModels = [];
|
|
5809
|
+
try {
|
|
5810
|
+
ollamaModels = await listOllamaLocalModels({
|
|
5811
|
+
baseUrl: nextProvider.baseUrl,
|
|
5812
|
+
headers: config.provider.type === "ollama" ? config.provider.headers : undefined,
|
|
5813
|
+
timeoutMs: nextProvider.timeoutMs,
|
|
5814
|
+
});
|
|
5815
|
+
if (ollamaModels.length === 0) {
|
|
5816
|
+
this.showWarning("No local Ollama models found at /api/tags. You can enter model manually.");
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
catch (error) {
|
|
5820
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5821
|
+
this.showWarning(`Failed to load Ollama models automatically: ${errorMsg}`);
|
|
5822
|
+
}
|
|
5823
|
+
const selectedModel = await this.selectSemanticModelFromCatalog("/semantic setup: ollama model", ollamaModels, currentModel || providerDefaults.ollama, { highlightLikelyEmbedding: true });
|
|
5824
|
+
if (!selectedModel) {
|
|
5825
|
+
this.showStatus("Semantic setup cancelled");
|
|
5826
|
+
return;
|
|
5827
|
+
}
|
|
5828
|
+
model = selectedModel;
|
|
5829
|
+
}
|
|
5830
|
+
else if (providerType === "custom_openai") {
|
|
5831
|
+
const defaultBase = (config.provider.type === "custom_openai" ? config.provider.baseUrl : undefined) ?? "http://127.0.0.1:8000/v1";
|
|
5832
|
+
const baseUrlInput = await this.showExtensionInput("/semantic setup: custom base URL", defaultBase);
|
|
5833
|
+
if (baseUrlInput === undefined) {
|
|
5834
|
+
this.showStatus("Semantic setup cancelled");
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
const baseUrl = baseUrlInput.trim();
|
|
5838
|
+
if (!baseUrl) {
|
|
5839
|
+
this.showWarning("Custom provider base URL cannot be empty.");
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
nextProvider.baseUrl = baseUrl;
|
|
5843
|
+
const defaultApiKeyEnv = (config.provider.type === "custom_openai" ? config.provider.apiKeyEnv : undefined) ?? "OPENAI_API_KEY";
|
|
5844
|
+
const apiKeyEnvInput = await this.showExtensionInput("/semantic setup: custom API key env (optional)", defaultApiKeyEnv);
|
|
5845
|
+
if (apiKeyEnvInput === undefined) {
|
|
5846
|
+
this.showStatus("Semantic setup cancelled");
|
|
5847
|
+
return;
|
|
5848
|
+
}
|
|
5849
|
+
nextProvider.apiKeyEnv = apiKeyEnvInput.trim() || undefined;
|
|
5850
|
+
const modelInput = await this.showExtensionInput("/semantic setup: custom model", currentModel);
|
|
5851
|
+
if (modelInput === undefined) {
|
|
5852
|
+
this.showStatus("Semantic setup cancelled");
|
|
5853
|
+
return;
|
|
5854
|
+
}
|
|
5855
|
+
const normalizedModel = modelInput.trim();
|
|
5856
|
+
if (!normalizedModel) {
|
|
5857
|
+
this.showWarning("Model cannot be empty.");
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
model = normalizedModel;
|
|
5861
|
+
}
|
|
5862
|
+
else {
|
|
5863
|
+
nextProvider.baseUrl = undefined;
|
|
5864
|
+
nextProvider.apiKeyEnv = undefined;
|
|
5865
|
+
let openRouterModels = [];
|
|
5866
|
+
try {
|
|
5867
|
+
openRouterModels = await listOpenRouterEmbeddingModels({
|
|
5868
|
+
timeoutMs: nextProvider.timeoutMs,
|
|
5869
|
+
authStorage: this.session.modelRegistry.authStorage,
|
|
5870
|
+
});
|
|
5871
|
+
if (openRouterModels.length === 0) {
|
|
5872
|
+
this.showWarning("OpenRouter embeddings catalog is empty. You can enter model manually.");
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
catch (error) {
|
|
5876
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5877
|
+
this.showWarning(`Failed to load OpenRouter embedding models automatically: ${errorMsg}`);
|
|
5878
|
+
}
|
|
5879
|
+
const selectedModel = await this.selectSemanticModelFromCatalog("/semantic setup: openrouter model", openRouterModels, currentModel || providerDefaults.openrouter);
|
|
5880
|
+
if (!selectedModel) {
|
|
5881
|
+
this.showStatus("Semantic setup cancelled");
|
|
5882
|
+
return;
|
|
5883
|
+
}
|
|
5884
|
+
model = selectedModel;
|
|
5885
|
+
}
|
|
5886
|
+
nextProvider.model = model;
|
|
5887
|
+
while (true) {
|
|
5888
|
+
const headersInput = await this.showExtensionInput("/semantic setup: headers (optional KEY=VALUE,CSV; press Enter to skip)", "");
|
|
5889
|
+
if (headersInput === undefined) {
|
|
5890
|
+
this.showStatus("Semantic setup cancelled");
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
const parsedHeaders = this.parseMcpKeyValueMapInput(headersInput);
|
|
5894
|
+
if (parsedHeaders.error) {
|
|
5895
|
+
this.showWarning(parsedHeaders.error);
|
|
5896
|
+
continue;
|
|
5897
|
+
}
|
|
5898
|
+
nextProvider.headers = parsedHeaders.value;
|
|
5899
|
+
break;
|
|
5900
|
+
}
|
|
5901
|
+
const nextConfig = {
|
|
5902
|
+
...config,
|
|
5903
|
+
enabled: true,
|
|
5904
|
+
provider: nextProvider,
|
|
5905
|
+
};
|
|
5906
|
+
const savedPath = upsertScopedSemanticSearchConfig(scope, nextConfig, cwd, agentDir);
|
|
5907
|
+
if (providerType === "openrouter") {
|
|
5908
|
+
await this.ensureOpenRouterSemanticCredentials();
|
|
5909
|
+
}
|
|
5910
|
+
this.showStatus(`Semantic setup saved (${scope})`);
|
|
5911
|
+
this.showCommandTextBlock("Semantic Setup", [
|
|
5912
|
+
`scope: ${scope}`,
|
|
5913
|
+
`provider: ${this.getSemanticSetupProviderLabel(providerType)}`,
|
|
5914
|
+
`model: ${nextProvider.model}`,
|
|
5915
|
+
`config: ${savedPath}`,
|
|
5916
|
+
`index_dir: ${getSemanticIndexDir(cwd, agentDir)}`,
|
|
5917
|
+
].join("\n"));
|
|
5918
|
+
}
|
|
5919
|
+
async runSemanticInteractiveMenu() {
|
|
5920
|
+
while (true) {
|
|
5921
|
+
let status;
|
|
5922
|
+
try {
|
|
5923
|
+
status = await this.createSemanticRuntime().status();
|
|
5924
|
+
}
|
|
5925
|
+
catch (error) {
|
|
5926
|
+
this.reportSemanticError(error, "status");
|
|
5927
|
+
}
|
|
5928
|
+
const summary = status
|
|
5929
|
+
? `configured=${status.configured ? "yes" : "no"} indexed=${status.indexed ? "yes" : "no"} stale=${status.stale ? "yes" : "no"}`
|
|
5930
|
+
: "status unavailable";
|
|
5931
|
+
const selected = await this.showExtensionSelector(`/semantic manager\n${summary}`, [
|
|
5932
|
+
"Configure provider/model",
|
|
5933
|
+
"Show status",
|
|
5934
|
+
"Index now",
|
|
5935
|
+
"Rebuild index",
|
|
5936
|
+
"Query index",
|
|
5937
|
+
"Show config/index paths",
|
|
5938
|
+
"Close",
|
|
5939
|
+
]);
|
|
5940
|
+
if (!selected || selected === "Close") {
|
|
5941
|
+
return;
|
|
5942
|
+
}
|
|
5943
|
+
if (selected === "Configure provider/model") {
|
|
5944
|
+
await this.runSemanticSetupWizard();
|
|
5945
|
+
continue;
|
|
5946
|
+
}
|
|
5947
|
+
if (selected === "Show status") {
|
|
5948
|
+
try {
|
|
5949
|
+
const result = await this.runWithExtensionLoader("Checking semantic index status...", async () => this.createSemanticRuntime().status());
|
|
5950
|
+
this.showCommandTextBlock("Semantic Status", this.formatSemanticStatusReport(result));
|
|
5951
|
+
}
|
|
5952
|
+
catch (error) {
|
|
5953
|
+
this.reportSemanticError(error, "status");
|
|
5954
|
+
}
|
|
5955
|
+
continue;
|
|
5956
|
+
}
|
|
5957
|
+
if (selected === "Index now") {
|
|
5958
|
+
try {
|
|
5959
|
+
const result = await this.runWithExtensionLoader("Indexing semantic embeddings...", async () => this.createSemanticRuntime().index());
|
|
5960
|
+
this.showStatus(`Semantic index updated (${result.processedFiles} files).`);
|
|
5961
|
+
this.showCommandTextBlock("Semantic Index", this.formatSemanticIndexReport(result));
|
|
5962
|
+
}
|
|
5963
|
+
catch (error) {
|
|
5964
|
+
this.reportSemanticError(error, "index");
|
|
5965
|
+
}
|
|
5966
|
+
continue;
|
|
5967
|
+
}
|
|
5968
|
+
if (selected === "Rebuild index") {
|
|
5969
|
+
try {
|
|
5970
|
+
const result = await this.runWithExtensionLoader("Rebuilding semantic index...", async () => this.createSemanticRuntime().rebuild());
|
|
5971
|
+
this.showStatus(`Semantic index rebuilt (${result.processedFiles} files).`);
|
|
5972
|
+
this.showCommandTextBlock("Semantic Rebuild", this.formatSemanticIndexReport(result));
|
|
5973
|
+
}
|
|
5974
|
+
catch (error) {
|
|
5975
|
+
this.reportSemanticError(error, "rebuild");
|
|
5976
|
+
}
|
|
5977
|
+
continue;
|
|
5978
|
+
}
|
|
5979
|
+
if (selected === "Query index") {
|
|
5980
|
+
const queryInput = await this.showExtensionInput("/semantic query", "where auth token is validated");
|
|
5981
|
+
if (queryInput === undefined)
|
|
5982
|
+
continue;
|
|
5983
|
+
const query = queryInput.trim();
|
|
5984
|
+
if (!query) {
|
|
5985
|
+
this.showWarning("Semantic query cannot be empty.");
|
|
5986
|
+
continue;
|
|
5987
|
+
}
|
|
5988
|
+
const topKInput = await this.showExtensionInput("/semantic query: top-k (optional, 1..20)", "8");
|
|
5989
|
+
if (topKInput === undefined)
|
|
5990
|
+
continue;
|
|
5991
|
+
const topKRaw = topKInput.trim();
|
|
5992
|
+
let topK = undefined;
|
|
5993
|
+
if (topKRaw) {
|
|
5994
|
+
const parsed = Number.parseInt(topKRaw, 10);
|
|
5995
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 20) {
|
|
5996
|
+
this.showWarning("top-k must be an integer between 1 and 20.");
|
|
5997
|
+
continue;
|
|
5998
|
+
}
|
|
5999
|
+
topK = parsed;
|
|
6000
|
+
}
|
|
6001
|
+
try {
|
|
6002
|
+
const result = await this.runWithExtensionLoader("Querying semantic index...", async () => this.createSemanticRuntime().query(query, topK));
|
|
6003
|
+
this.showCommandTextBlock("Semantic Query", this.formatSemanticQueryReport(result));
|
|
6004
|
+
}
|
|
6005
|
+
catch (error) {
|
|
6006
|
+
this.reportSemanticError(error, "query");
|
|
6007
|
+
}
|
|
6008
|
+
continue;
|
|
6009
|
+
}
|
|
6010
|
+
if (selected === "Show config/index paths") {
|
|
6011
|
+
const runtime = this.createSemanticRuntime();
|
|
6012
|
+
const cwd = this.sessionManager.getCwd();
|
|
6013
|
+
const agentDir = getAgentDir();
|
|
6014
|
+
try {
|
|
6015
|
+
const semanticStatus = await this.runWithExtensionLoader("Loading semantic paths...", async () => runtime.status());
|
|
6016
|
+
this.showCommandTextBlock("Semantic Paths", [
|
|
6017
|
+
`user_config: ${getSemanticConfigPath("user", cwd, agentDir)}`,
|
|
6018
|
+
`project_config: ${getSemanticConfigPath("project", cwd, agentDir)}`,
|
|
6019
|
+
`index_dir: ${semanticStatus.indexPath}`,
|
|
6020
|
+
].join("\n"));
|
|
6021
|
+
}
|
|
6022
|
+
catch (error) {
|
|
6023
|
+
this.reportSemanticError(error, "status");
|
|
6024
|
+
}
|
|
6025
|
+
continue;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
reportSemanticError(error, context) {
|
|
6030
|
+
if (error instanceof SemanticConfigMissingError) {
|
|
6031
|
+
this.showWarning("Semantic search is not configured. Run /semantic setup.");
|
|
6032
|
+
this.showCommandTextBlock("Semantic Config", [
|
|
6033
|
+
`user_config: ${error.userConfigPath}`,
|
|
6034
|
+
`project_config: ${error.projectConfigPath}`,
|
|
6035
|
+
"next: /semantic setup",
|
|
6036
|
+
].join("\n"));
|
|
6037
|
+
return;
|
|
6038
|
+
}
|
|
6039
|
+
if (error instanceof SemanticRebuildRequiredError) {
|
|
6040
|
+
this.showWarning(error.message);
|
|
6041
|
+
this.showWarning("Run /semantic rebuild.");
|
|
6042
|
+
return;
|
|
6043
|
+
}
|
|
6044
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6045
|
+
this.showError(`Semantic ${context} failed: ${message}`);
|
|
6046
|
+
}
|
|
6047
|
+
async handleSemanticCommand(text) {
|
|
6048
|
+
const args = this.parseSlashArgs(text).slice(1);
|
|
6049
|
+
if (args.length === 0 || (args[0]?.toLowerCase() ?? "") === "ui") {
|
|
6050
|
+
await this.runSemanticInteractiveMenu();
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
const subcommand = (args[0] ?? "").toLowerCase();
|
|
6054
|
+
const rest = args.slice(1);
|
|
6055
|
+
if (subcommand === "help" || subcommand === "-h" || subcommand === "--help") {
|
|
6056
|
+
this.showCommandTextBlock("Semantic Help", [
|
|
6057
|
+
"Usage:",
|
|
6058
|
+
" /semantic",
|
|
6059
|
+
" /semantic ui",
|
|
6060
|
+
" /semantic setup [--scope user|project]",
|
|
6061
|
+
" /semantic status",
|
|
6062
|
+
" /semantic index",
|
|
6063
|
+
" /semantic rebuild",
|
|
6064
|
+
" /semantic query <text> [--top-k N]",
|
|
6065
|
+
" /semantic help",
|
|
6066
|
+
].join("\n"));
|
|
6067
|
+
return;
|
|
6068
|
+
}
|
|
6069
|
+
if (subcommand === "setup") {
|
|
6070
|
+
const parsedScope = this.parseSemanticScopeOptions(rest);
|
|
6071
|
+
if (parsedScope.error) {
|
|
6072
|
+
this.showWarning(parsedScope.error);
|
|
6073
|
+
return;
|
|
6074
|
+
}
|
|
6075
|
+
if (parsedScope.rest.length > 0) {
|
|
6076
|
+
this.showWarning(`Unexpected arguments for /semantic setup: ${parsedScope.rest.join(" ")}`);
|
|
6077
|
+
return;
|
|
6078
|
+
}
|
|
6079
|
+
await this.runSemanticSetupWizard(parsedScope.scope);
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
6082
|
+
if (subcommand === "status") {
|
|
6083
|
+
try {
|
|
6084
|
+
const result = await this.runWithExtensionLoader("Checking semantic index status...", async () => this.createSemanticRuntime().status());
|
|
6085
|
+
this.showCommandTextBlock("Semantic Status", this.formatSemanticStatusReport(result));
|
|
6086
|
+
}
|
|
6087
|
+
catch (error) {
|
|
6088
|
+
this.reportSemanticError(error, "status");
|
|
6089
|
+
}
|
|
6090
|
+
return;
|
|
6091
|
+
}
|
|
6092
|
+
if (subcommand === "index") {
|
|
6093
|
+
try {
|
|
6094
|
+
const result = await this.runWithExtensionLoader("Indexing semantic embeddings...", async () => this.createSemanticRuntime().index());
|
|
6095
|
+
this.showStatus(`Semantic index updated (${result.processedFiles} files).`);
|
|
6096
|
+
this.showCommandTextBlock("Semantic Index", this.formatSemanticIndexReport(result));
|
|
6097
|
+
}
|
|
6098
|
+
catch (error) {
|
|
6099
|
+
this.reportSemanticError(error, "index");
|
|
6100
|
+
}
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
if (subcommand === "rebuild") {
|
|
6104
|
+
try {
|
|
6105
|
+
const result = await this.runWithExtensionLoader("Rebuilding semantic index...", async () => this.createSemanticRuntime().rebuild());
|
|
6106
|
+
this.showStatus(`Semantic index rebuilt (${result.processedFiles} files).`);
|
|
6107
|
+
this.showCommandTextBlock("Semantic Rebuild", this.formatSemanticIndexReport(result));
|
|
6108
|
+
}
|
|
6109
|
+
catch (error) {
|
|
6110
|
+
this.reportSemanticError(error, "rebuild");
|
|
6111
|
+
}
|
|
6112
|
+
return;
|
|
6113
|
+
}
|
|
6114
|
+
if (subcommand === "query") {
|
|
6115
|
+
const parsed = this.parseSemanticTopKOptions(rest);
|
|
6116
|
+
if (parsed.error) {
|
|
6117
|
+
this.showWarning(parsed.error);
|
|
6118
|
+
return;
|
|
6119
|
+
}
|
|
6120
|
+
const query = parsed.rest.join(" ").trim();
|
|
6121
|
+
if (!query) {
|
|
6122
|
+
this.showWarning("Usage: /semantic query <text> [--top-k N]");
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
try {
|
|
6126
|
+
const result = await this.runWithExtensionLoader("Querying semantic index...", async () => this.createSemanticRuntime().query(query, parsed.topK));
|
|
6127
|
+
this.showCommandTextBlock("Semantic Query", this.formatSemanticQueryReport(result));
|
|
6128
|
+
}
|
|
6129
|
+
catch (error) {
|
|
6130
|
+
this.reportSemanticError(error, "query");
|
|
6131
|
+
}
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
this.showWarning(`Unknown /semantic subcommand "${subcommand}". Use /semantic help.`);
|
|
6135
|
+
}
|
|
5502
6136
|
parseCheckpointNameFromLabel(label) {
|
|
5503
6137
|
if (!label)
|
|
5504
6138
|
return undefined;
|
|
@@ -5680,6 +6314,7 @@ export class InteractiveMode {
|
|
|
5680
6314
|
const hasModelIssues = checks.some((check) => (check.label === "Active model" || check.label === "Active model auth" || check.label === "Available models") &&
|
|
5681
6315
|
check.level === "fail");
|
|
5682
6316
|
const hasMcpIssues = checks.some((check) => check.label === "MCP servers" && check.level !== "ok");
|
|
6317
|
+
const hasSemanticIssues = checks.some((check) => check.label === "Semantic index" && check.level !== "ok");
|
|
5683
6318
|
const hasResourceIssues = checks.some((check) => check.label === "Resources" && check.level !== "ok");
|
|
5684
6319
|
while (true) {
|
|
5685
6320
|
const options = [];
|
|
@@ -5693,6 +6328,9 @@ export class InteractiveMode {
|
|
|
5693
6328
|
}
|
|
5694
6329
|
options.push("Refresh MCP runtime");
|
|
5695
6330
|
}
|
|
6331
|
+
if (hasSemanticIssues) {
|
|
6332
|
+
options.push("Open semantic manager");
|
|
6333
|
+
}
|
|
5696
6334
|
if (this.permissionMode === "yolo") {
|
|
5697
6335
|
options.push("Set permissions mode to ask");
|
|
5698
6336
|
}
|
|
@@ -5723,6 +6361,10 @@ export class InteractiveMode {
|
|
|
5723
6361
|
this.showStatus("MCP servers refreshed");
|
|
5724
6362
|
continue;
|
|
5725
6363
|
}
|
|
6364
|
+
if (selected === "Open semantic manager") {
|
|
6365
|
+
await this.handleSemanticCommand("/semantic");
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
5726
6368
|
if (selected === "Set permissions mode to ask") {
|
|
5727
6369
|
this.permissionMode = "ask";
|
|
5728
6370
|
this.settingsManager.setPermissionMode("ask");
|
|
@@ -5734,7 +6376,15 @@ export class InteractiveMode {
|
|
|
5734
6376
|
return;
|
|
5735
6377
|
}
|
|
5736
6378
|
if (selected === "Show auth/models paths") {
|
|
5737
|
-
|
|
6379
|
+
const cwd = this.sessionManager.getCwd();
|
|
6380
|
+
const agentDir = getAgentDir();
|
|
6381
|
+
this.showCommandTextBlock("Runtime Paths", [
|
|
6382
|
+
`auth.json: ${getAuthPath()}`,
|
|
6383
|
+
`models.json: ${getModelsPath()}`,
|
|
6384
|
+
`semantic(user): ${getSemanticConfigPath("user", cwd, agentDir)}`,
|
|
6385
|
+
`semantic(project): ${getSemanticConfigPath("project", cwd, agentDir)}`,
|
|
6386
|
+
`semantic(index): ${getSemanticIndexDir(cwd, agentDir)}`,
|
|
6387
|
+
].join("\n"));
|
|
5738
6388
|
continue;
|
|
5739
6389
|
}
|
|
5740
6390
|
}
|
|
@@ -5761,6 +6411,14 @@ export class InteractiveMode {
|
|
|
5761
6411
|
const mcpDisabled = mcpStatuses.filter((status) => !status.enabled).length;
|
|
5762
6412
|
const cliToolStatuses = resolveDoctorCliToolStatuses();
|
|
5763
6413
|
const missingCliTools = cliToolStatuses.filter((status) => !status.available).map((status) => status.tool);
|
|
6414
|
+
let semanticStatus;
|
|
6415
|
+
let semanticStatusError;
|
|
6416
|
+
try {
|
|
6417
|
+
semanticStatus = await this.createSemanticRuntime().status();
|
|
6418
|
+
}
|
|
6419
|
+
catch (error) {
|
|
6420
|
+
semanticStatusError = error instanceof Error ? error.message : String(error);
|
|
6421
|
+
}
|
|
5764
6422
|
const checks = [];
|
|
5765
6423
|
const addCheck = (level, label, detail, fix) => checks.push({ level, label, detail, fix });
|
|
5766
6424
|
if (!model) {
|
|
@@ -5802,6 +6460,31 @@ export class InteractiveMode {
|
|
|
5802
6460
|
else {
|
|
5803
6461
|
addCheck("ok", "MCP servers", `${mcpConnected} connected, ${mcpDisabled} disabled`);
|
|
5804
6462
|
}
|
|
6463
|
+
if (semanticStatusError) {
|
|
6464
|
+
addCheck("fail", "Semantic index", semanticStatusError, "Run /semantic setup and retry /semantic status.");
|
|
6465
|
+
}
|
|
6466
|
+
else if (!semanticStatus) {
|
|
6467
|
+
addCheck("warn", "Semantic index", "Status unavailable", "Run /semantic setup.");
|
|
6468
|
+
}
|
|
6469
|
+
else if (!semanticStatus.configured) {
|
|
6470
|
+
addCheck("warn", "Semantic index", "Not configured", `Run /semantic setup (user: ${semanticStatus.configPathUser} or project: ${semanticStatus.configPathProject}).`);
|
|
6471
|
+
}
|
|
6472
|
+
else if (!semanticStatus.enabled) {
|
|
6473
|
+
addCheck("warn", "Semantic index", "Configured but disabled", "Enable semanticSearch.enabled or rerun /semantic setup.");
|
|
6474
|
+
}
|
|
6475
|
+
else if (!semanticStatus.indexed) {
|
|
6476
|
+
addCheck("warn", "Semantic index", "Configured but index is missing", "Run /semantic index.");
|
|
6477
|
+
}
|
|
6478
|
+
else if (semanticStatus.stale) {
|
|
6479
|
+
const requiresRebuild = semanticStatus.staleReason === "provider_changed" ||
|
|
6480
|
+
semanticStatus.staleReason === "chunking_changed" ||
|
|
6481
|
+
semanticStatus.staleReason === "index_filters_changed" ||
|
|
6482
|
+
semanticStatus.staleReason === "dimension_mismatch";
|
|
6483
|
+
addCheck("warn", "Semantic index", `Indexed but stale${semanticStatus.staleReason ? ` (${semanticStatus.staleReason})` : ""}`, requiresRebuild ? "Run /semantic rebuild." : "Run /semantic index.");
|
|
6484
|
+
}
|
|
6485
|
+
else {
|
|
6486
|
+
addCheck("ok", "Semantic index", `${semanticStatus.provider}/${semanticStatus.model} · files=${semanticStatus.files} chunks=${semanticStatus.chunks}`);
|
|
6487
|
+
}
|
|
5805
6488
|
if (missingCliTools.length > 0) {
|
|
5806
6489
|
addCheck("warn", "CLI toolchain", `${cliToolStatuses.length - missingCliTools.length}/${cliToolStatuses.length} available (missing: ${missingCliTools.join(", ")})`, `Install missing CLI tools: ${missingCliTools.join(", ")}.`);
|
|
5807
6490
|
}
|