open-multi-agent-kit 0.80.4 → 0.80.6
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 +21 -0
- package/dist/config.d.ts +4 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -15
- package/dist/config.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +6 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +82 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +606 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/harness-control-events.d.ts +79 -0
- package/dist/core/harness-control-events.d.ts.map +1 -0
- package/dist/core/harness-control-events.js +339 -0
- package/dist/core/harness-control-events.js.map +1 -0
- package/dist/core/harness-control-replay.d.ts +26 -0
- package/dist/core/harness-control-replay.d.ts.map +1 -0
- package/dist/core/harness-control-replay.js +155 -0
- package/dist/core/harness-control-replay.js.map +1 -0
- package/dist/core/keybindings.d.ts +12 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +31 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mcp-inventory.d.ts +40 -0
- package/dist/core/mcp-inventory.d.ts.map +1 -0
- package/dist/core/mcp-inventory.js +104 -0
- package/dist/core/mcp-inventory.js.map +1 -0
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/spec-kit/compiler.d.ts +67 -0
- package/dist/core/spec-kit/compiler.d.ts.map +1 -0
- package/dist/core/spec-kit/compiler.js +268 -0
- package/dist/core/spec-kit/compiler.js.map +1 -0
- package/dist/core/transaction-coordinator.d.ts +30 -0
- package/dist/core/transaction-coordinator.d.ts.map +1 -0
- package/dist/core/transaction-coordinator.js +173 -0
- package/dist/core/transaction-coordinator.js.map +1 -0
- package/dist/migrations.d.ts +35 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +409 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/control-panel.d.ts +29 -0
- package/dist/modes/interactive/components/control-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/control-panel.js +90 -0
- package/dist/modes/interactive/components/control-panel.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +21 -13
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +3 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +3 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/mcp-selector.d.ts +28 -0
- package/dist/modes/interactive/components/mcp-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/mcp-selector.js +138 -0
- package/dist/modes/interactive/components/mcp-selector.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +12 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +120 -27
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +19 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +21 -8
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skills-selector.d.ts +32 -0
- package/dist/modes/interactive/components/skills-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/skills-selector.js +130 -0
- package/dist/modes/interactive/components/skills-selector.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +7 -8
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +7 -48
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +340 -97
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/catppuccin-mocha.json +86 -0
- package/dist/modes/interactive/theme/dracula.json +86 -0
- package/dist/modes/interactive/theme/gruvbox-dark.json +86 -0
- package/dist/modes/interactive/theme/kanagawa-dragon.json +86 -0
- package/dist/modes/interactive/theme/omk-control-panel.json +91 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +41 -16
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/theme/tokyo-night-storm.json +86 -0
- package/docs/compaction.md +21 -8
- package/docs/quickstart.md +1 -1
- package/docs/themes.md +10 -2
- package/docs/usage.md +2 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getProviders, } from "@earendil-works/omk-ai";
|
|
9
|
+
import { clampThinkingLevel, getProviders, getSupportedThinkingLevels, modelsAreEqual, } from "@earendil-works/omk-ai";
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/omk-tui";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -16,6 +16,7 @@ import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime
|
|
|
16
16
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
17
17
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
18
18
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
19
|
+
import { loadMcpInventory } from "../../core/mcp-inventory.js";
|
|
19
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
21
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
21
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
@@ -24,6 +25,7 @@ import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../cor
|
|
|
24
25
|
import { SessionManager } from "../../core/session-manager.js";
|
|
25
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
26
27
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
28
|
+
import { runHarnessControlTransaction, runHarnessControlTransactionSync } from "../../core/transaction-coordinator.js";
|
|
27
29
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
28
30
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
29
31
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
@@ -38,6 +40,7 @@ import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
|
38
40
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
39
41
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
40
42
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
43
|
+
import { ControlPanelComponent } from "./components/control-panel.js";
|
|
41
44
|
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
42
45
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
43
46
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
@@ -50,12 +53,14 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
|
50
53
|
import { FooterComponent } from "./components/footer.js";
|
|
51
54
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
52
55
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
56
|
+
import { McpSelectorComponent } from "./components/mcp-selector.js";
|
|
53
57
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
54
58
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
55
59
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
56
60
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
57
|
-
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
61
|
+
import { SettingsSelectorComponent, ThinkingSelectorComponent } from "./components/settings-selector.js";
|
|
58
62
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
63
|
+
import { SkillsSelectorComponent } from "./components/skills-selector.js";
|
|
59
64
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
60
65
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
61
66
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
@@ -67,14 +72,21 @@ function isExpandable(obj) {
|
|
|
67
72
|
class ExpandableText extends Text {
|
|
68
73
|
getCollapsedText;
|
|
69
74
|
getExpandedText;
|
|
75
|
+
expanded;
|
|
70
76
|
constructor(getCollapsedText, getExpandedText, expanded = false, paddingX = 0, paddingY = 0) {
|
|
71
77
|
super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);
|
|
72
78
|
this.getCollapsedText = getCollapsedText;
|
|
73
79
|
this.getExpandedText = getExpandedText;
|
|
80
|
+
this.expanded = expanded;
|
|
74
81
|
}
|
|
75
82
|
setExpanded(expanded) {
|
|
83
|
+
this.expanded = expanded;
|
|
76
84
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
77
85
|
}
|
|
86
|
+
invalidate() {
|
|
87
|
+
this.setText(this.expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
88
|
+
super.invalidate();
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
80
92
|
function isDeadTerminalError(error) {
|
|
@@ -315,6 +327,20 @@ export class InteractiveMode {
|
|
|
315
327
|
name: command.name,
|
|
316
328
|
description: command.description,
|
|
317
329
|
}));
|
|
330
|
+
const thinkCommand = slashCommands.find((command) => command.name === "think");
|
|
331
|
+
if (thinkCommand) {
|
|
332
|
+
thinkCommand.getArgumentCompletions = (prefix) => {
|
|
333
|
+
const levels = this.session.getAvailableThinkingLevels();
|
|
334
|
+
const filtered = fuzzyFilter(levels, prefix, (level) => level);
|
|
335
|
+
if (filtered.length === 0)
|
|
336
|
+
return null;
|
|
337
|
+
return filtered.map((level) => ({
|
|
338
|
+
value: level,
|
|
339
|
+
label: level,
|
|
340
|
+
description: level === this.session.thinkingLevel ? "current" : undefined,
|
|
341
|
+
}));
|
|
342
|
+
};
|
|
343
|
+
}
|
|
318
344
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
319
345
|
if (modelCommand) {
|
|
320
346
|
modelCommand.getArgumentCompletions = (prefix) => {
|
|
@@ -324,21 +350,48 @@ export class InteractiveMode {
|
|
|
324
350
|
: this.session.modelRegistry.getAvailable();
|
|
325
351
|
if (models.length === 0)
|
|
326
352
|
return null;
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
353
|
+
const normalizedPrefix = prefix.trimStart();
|
|
354
|
+
const slashIndex = normalizedPrefix.indexOf("/");
|
|
355
|
+
const providerPrefix = slashIndex >= 0 ? normalizedPrefix.slice(0, slashIndex) : normalizedPrefix;
|
|
356
|
+
const modelPrefix = slashIndex >= 0 ? normalizedPrefix.slice(slashIndex + 1) : "";
|
|
357
|
+
const providers = Array.from(new Set(models.map((model) => model.provider))).sort((a, b) => a.localeCompare(b));
|
|
358
|
+
if (slashIndex === -1) {
|
|
359
|
+
const providerItems = fuzzyFilter(providers.map((provider) => ({ provider })), providerPrefix, (item) => item.provider).map((item) => ({
|
|
360
|
+
value: `${item.provider}/`,
|
|
361
|
+
label: item.provider,
|
|
362
|
+
description: "provider",
|
|
363
|
+
}));
|
|
364
|
+
const modelItems = fuzzyFilter(models.map((model) => ({
|
|
365
|
+
id: model.id,
|
|
366
|
+
provider: model.provider,
|
|
367
|
+
label: `${model.provider}/${model.id}`,
|
|
368
|
+
})), normalizedPrefix, (item) => `${item.id} ${item.provider} ${item.provider}/${item.id}`).map((item) => ({
|
|
369
|
+
value: item.label,
|
|
370
|
+
label: item.id,
|
|
371
|
+
description: item.provider,
|
|
372
|
+
}));
|
|
373
|
+
const combined = [...providerItems, ...modelItems].slice(0, 50);
|
|
374
|
+
return combined.length > 0 ? combined : null;
|
|
375
|
+
}
|
|
376
|
+
const matchedProvider = providers.find((provider) => provider === providerPrefix);
|
|
377
|
+
if (!matchedProvider) {
|
|
336
378
|
return null;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
379
|
+
}
|
|
380
|
+
const providerModels = models
|
|
381
|
+
.filter((model) => model.provider === matchedProvider)
|
|
382
|
+
.map((model) => ({
|
|
383
|
+
id: model.id,
|
|
384
|
+
provider: model.provider,
|
|
385
|
+
label: `${model.provider}/${model.id}`,
|
|
341
386
|
}));
|
|
387
|
+
const filtered = fuzzyFilter(providerModels, modelPrefix, (item) => `${item.id} ${item.provider}`);
|
|
388
|
+
return filtered.length > 0
|
|
389
|
+
? filtered.map((item) => ({
|
|
390
|
+
value: item.label,
|
|
391
|
+
label: item.id,
|
|
392
|
+
description: item.provider,
|
|
393
|
+
}))
|
|
394
|
+
: null;
|
|
342
395
|
};
|
|
343
396
|
}
|
|
344
397
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
@@ -436,10 +489,9 @@ export class InteractiveMode {
|
|
|
436
489
|
this.ui.addChild(this.headerContainer);
|
|
437
490
|
// Add header with keybindings from config (unless silenced)
|
|
438
491
|
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
439
|
-
|
|
440
|
-
// Build startup instructions using keybinding hint helpers
|
|
492
|
+
// Build startup instructions lazily so theme changes do not retain stale ANSI sequences.
|
|
441
493
|
const hint = (keybinding, description) => keyHint(keybinding, description);
|
|
442
|
-
const expandedInstructions = [
|
|
494
|
+
const expandedInstructions = () => [
|
|
443
495
|
hint("app.interrupt", "to interrupt"),
|
|
444
496
|
hint("app.clear", "to clear"),
|
|
445
497
|
rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
|
|
@@ -460,16 +512,23 @@ export class InteractiveMode {
|
|
|
460
512
|
hint("app.clipboard.pasteImage", "to paste image"),
|
|
461
513
|
rawKeyHint("drop files", "to attach"),
|
|
462
514
|
].join("\n");
|
|
463
|
-
const compactInstructions = [
|
|
515
|
+
const compactInstructions = () => [
|
|
464
516
|
hint("app.interrupt", "interrupt"),
|
|
465
517
|
rawKeyHint(`${keyText("app.clear")}/${keyText("app.exit")}`, "clear/exit"),
|
|
466
518
|
rawKeyHint("/", "commands"),
|
|
467
519
|
rawKeyHint("!", "bash"),
|
|
468
520
|
hint("app.tools.expand", "more"),
|
|
469
521
|
].join(theme.fg("muted", " · "));
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
522
|
+
const controlPanel = new ControlPanelComponent({
|
|
523
|
+
appName: APP_NAME,
|
|
524
|
+
version: this.version,
|
|
525
|
+
compactInstructions,
|
|
526
|
+
expandedInstructions,
|
|
527
|
+
compactOnboarding: () => `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`,
|
|
528
|
+
onboarding: () => `OMK can explain its own features and look up its docs. Ask it how to use or extend OMK.`,
|
|
529
|
+
});
|
|
530
|
+
controlPanel.setExpanded(this.getStartupExpansionState());
|
|
531
|
+
this.builtInHeader = controlPanel;
|
|
473
532
|
// Setup UI layout
|
|
474
533
|
this.headerContainer.addChild(new Spacer(1));
|
|
475
534
|
this.headerContainer.addChild(this.builtInHeader);
|
|
@@ -980,22 +1039,53 @@ export class InteractiveMode {
|
|
|
980
1039
|
}
|
|
981
1040
|
return lines.join("\n");
|
|
982
1041
|
}
|
|
1042
|
+
formatDiagnosticsSummary(diagnostics) {
|
|
1043
|
+
const collisionDiagnostics = diagnostics.filter((d) => d.type === "collision" && d.collision);
|
|
1044
|
+
const collisionNames = new Set(collisionDiagnostics.map((d) => `${d.collision?.resourceType ?? "resource"}:${d.collision?.name ?? "unknown"}`));
|
|
1045
|
+
const errorCount = diagnostics.filter((d) => d.type === "error").length;
|
|
1046
|
+
const warningCount = diagnostics.filter((d) => d.type === "warning").length;
|
|
1047
|
+
const parts = [];
|
|
1048
|
+
if (collisionDiagnostics.length > 0) {
|
|
1049
|
+
parts.push(`${collisionDiagnostics.length} duplicate resource${collisionDiagnostics.length === 1 ? "" : "s"} skipped across ${collisionNames.size} name${collisionNames.size === 1 ? "" : "s"}`);
|
|
1050
|
+
}
|
|
1051
|
+
if (errorCount > 0) {
|
|
1052
|
+
parts.push(`${errorCount} error${errorCount === 1 ? "" : "s"}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (warningCount > 0) {
|
|
1055
|
+
parts.push(`${warningCount} warning${warningCount === 1 ? "" : "s"}`);
|
|
1056
|
+
}
|
|
1057
|
+
const summary = parts.length > 0 ? parts.join(", ") : "No issues";
|
|
1058
|
+
return theme.fg("dim", ` ${summary}. ${keyHint("app.tools.expand", "for details")}`);
|
|
1059
|
+
}
|
|
983
1060
|
showLoadedResources(options) {
|
|
984
1061
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
985
1062
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
986
1063
|
if (!showListing && !showDiagnostics) {
|
|
987
1064
|
return;
|
|
988
1065
|
}
|
|
1066
|
+
const diagnosticsForStartup = (diagnostics) => {
|
|
1067
|
+
if (showListing) {
|
|
1068
|
+
return [...diagnostics];
|
|
1069
|
+
}
|
|
1070
|
+
return diagnostics.filter((diagnostic) => diagnostic.type !== "collision");
|
|
1071
|
+
};
|
|
989
1072
|
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
990
1073
|
const formatCompactList = (items, options) => {
|
|
991
1074
|
const labels = items.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
992
1075
|
if (options?.sort !== false) {
|
|
993
1076
|
labels.sort((a, b) => a.localeCompare(b));
|
|
994
1077
|
}
|
|
995
|
-
|
|
1078
|
+
const maxItems = options?.maxItems ?? 24;
|
|
1079
|
+
const visibleLabels = labels.slice(0, maxItems);
|
|
1080
|
+
const overflow = labels.length - visibleLabels.length;
|
|
1081
|
+
const suffix = overflow > 0 ? `, … +${overflow} more (${keyText("app.tools.expand")} for full list)` : "";
|
|
1082
|
+
const body = visibleLabels.length > 0 ? `${visibleLabels.join(", ")}${suffix}` : "(none)";
|
|
1083
|
+
return theme.fg("dim", ` ${body}`);
|
|
996
1084
|
};
|
|
997
1085
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
998
|
-
const
|
|
1086
|
+
const getCollapsedBody = typeof collapsedBody === "function" ? collapsedBody : () => collapsedBody;
|
|
1087
|
+
const getExpandedBody = typeof expandedBody === "function" ? expandedBody : () => expandedBody;
|
|
1088
|
+
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${getCollapsedBody()}`, () => `${sectionHeader(name, color)}\n${getExpandedBody()}`, this.getStartupExpansionState(), 0, 0);
|
|
999
1089
|
this.chatContainer.addChild(section);
|
|
1000
1090
|
this.chatContainer.addChild(new Spacer(1));
|
|
1001
1091
|
};
|
|
@@ -1032,27 +1122,21 @@ export class InteractiveMode {
|
|
|
1032
1122
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
1033
1123
|
if (contextFiles.length > 0) {
|
|
1034
1124
|
this.chatContainer.addChild(new Spacer(1));
|
|
1035
|
-
|
|
1036
|
-
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1037
|
-
.join("\n");
|
|
1038
|
-
const contextCompactList = formatCompactList(contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)), { sort: false });
|
|
1039
|
-
addLoadedSection("Context", contextCompactList, contextList);
|
|
1125
|
+
addLoadedSection("Context", () => formatCompactList(contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)), { sort: false }), () => contextFiles.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`)).join("\n"));
|
|
1040
1126
|
}
|
|
1041
1127
|
const skills = skillsResult.skills;
|
|
1042
1128
|
if (skills.length > 0) {
|
|
1043
1129
|
const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
|
|
1044
|
-
|
|
1130
|
+
addLoadedSection("Skills", () => formatCompactList(skills.map((skill) => skill.name)), () => this.formatScopeGroups(groups, {
|
|
1045
1131
|
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1046
1132
|
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1047
|
-
});
|
|
1048
|
-
const skillCompactList = formatCompactList(skills.map((skill) => skill.name));
|
|
1049
|
-
addLoadedSection("Skills", skillCompactList, skillList);
|
|
1133
|
+
}));
|
|
1050
1134
|
}
|
|
1051
1135
|
const templates = this.session.promptTemplates;
|
|
1052
1136
|
if (templates.length > 0) {
|
|
1053
1137
|
const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
|
|
1054
1138
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
1055
|
-
|
|
1139
|
+
addLoadedSection("Prompts", () => formatCompactList(templates.map((template) => `/${template.name}`)), () => this.formatScopeGroups(groups, {
|
|
1056
1140
|
formatPath: (item) => {
|
|
1057
1141
|
const template = templateByPath.get(item.path);
|
|
1058
1142
|
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
@@ -1061,18 +1145,14 @@ export class InteractiveMode {
|
|
|
1061
1145
|
const template = templateByPath.get(item.path);
|
|
1062
1146
|
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
1063
1147
|
},
|
|
1064
|
-
});
|
|
1065
|
-
const promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`));
|
|
1066
|
-
addLoadedSection("Prompts", promptCompactList, templateList);
|
|
1148
|
+
}));
|
|
1067
1149
|
}
|
|
1068
1150
|
if (extensions.length > 0) {
|
|
1069
1151
|
const groups = this.buildScopeGroups(extensions);
|
|
1070
|
-
|
|
1152
|
+
addLoadedSection("Extensions", () => formatCompactList(this.getCompactExtensionLabels(extensions)), () => this.formatScopeGroups(groups, {
|
|
1071
1153
|
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
|
|
1072
1154
|
formatPackagePath: (item) => this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
|
1073
|
-
});
|
|
1074
|
-
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
|
1075
|
-
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
|
1155
|
+
}), "mdHeading");
|
|
1076
1156
|
}
|
|
1077
1157
|
// Show loaded themes (excluding built-in)
|
|
1078
1158
|
const loadedThemes = themesResult.themes;
|
|
@@ -1082,27 +1162,24 @@ export class InteractiveMode {
|
|
|
1082
1162
|
path: loadedTheme.sourcePath,
|
|
1083
1163
|
sourceInfo: loadedTheme.sourceInfo,
|
|
1084
1164
|
})));
|
|
1085
|
-
|
|
1165
|
+
addLoadedSection("Themes", () => formatCompactList(customThemes.map((loadedTheme) => loadedTheme.name ??
|
|
1166
|
+
this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo))), () => this.formatScopeGroups(groups, {
|
|
1086
1167
|
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1087
1168
|
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1088
|
-
});
|
|
1089
|
-
const themeCompactList = formatCompactList(customThemes.map((loadedTheme) => loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo)));
|
|
1090
|
-
addLoadedSection("Themes", themeCompactList, themeList);
|
|
1169
|
+
}));
|
|
1091
1170
|
}
|
|
1092
1171
|
}
|
|
1093
1172
|
if (showDiagnostics) {
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
this.
|
|
1099
|
-
}
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1105
|
-
}
|
|
1173
|
+
const addDiagnosticsSection = (name, diagnostics) => {
|
|
1174
|
+
if (diagnostics.length === 0) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
addLoadedSection(name, () => this.formatDiagnosticsSummary(diagnostics), () => this.formatDiagnostics(diagnostics, sourceInfos), "warning");
|
|
1178
|
+
};
|
|
1179
|
+
const skillDiagnostics = diagnosticsForStartup(skillsResult.diagnostics);
|
|
1180
|
+
addDiagnosticsSection("Skill conflicts", skillDiagnostics);
|
|
1181
|
+
const promptDiagnostics = diagnosticsForStartup(promptsResult.diagnostics);
|
|
1182
|
+
addDiagnosticsSection("Prompt conflicts", promptDiagnostics);
|
|
1106
1183
|
const extensionDiagnostics = [];
|
|
1107
1184
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
1108
1185
|
if (extensionErrors.length > 0) {
|
|
@@ -1115,17 +1192,9 @@ export class InteractiveMode {
|
|
|
1115
1192
|
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
1116
1193
|
const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
|
|
1117
1194
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1122
|
-
}
|
|
1123
|
-
const themeDiagnostics = themesResult.diagnostics;
|
|
1124
|
-
if (themeDiagnostics.length > 0) {
|
|
1125
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1126
|
-
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1127
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1128
|
-
}
|
|
1195
|
+
addDiagnosticsSection("Extension issues", extensionDiagnostics);
|
|
1196
|
+
const themeDiagnostics = diagnosticsForStartup(themesResult.diagnostics);
|
|
1197
|
+
addDiagnosticsSection("Theme conflicts", themeDiagnostics);
|
|
1129
1198
|
}
|
|
1130
1199
|
}
|
|
1131
1200
|
/**
|
|
@@ -1999,6 +2068,12 @@ export class InteractiveMode {
|
|
|
1999
2068
|
this.editor.setText("");
|
|
2000
2069
|
return;
|
|
2001
2070
|
}
|
|
2071
|
+
if (text === "/think" || text.startsWith("/think ")) {
|
|
2072
|
+
const level = text.startsWith("/think ") ? text.slice(7).trim() : undefined;
|
|
2073
|
+
this.editor.setText("");
|
|
2074
|
+
this.handleThinkCommand(level);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2002
2077
|
if (text === "/scoped-models") {
|
|
2003
2078
|
this.editor.setText("");
|
|
2004
2079
|
await this.showModelsSelector();
|
|
@@ -2091,6 +2166,16 @@ export class InteractiveMode {
|
|
|
2091
2166
|
await this.handleReloadCommand();
|
|
2092
2167
|
return;
|
|
2093
2168
|
}
|
|
2169
|
+
if (text === "/mcp") {
|
|
2170
|
+
this.editor.setText("");
|
|
2171
|
+
this.handleMcpCommand();
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
if (text === "/skills") {
|
|
2175
|
+
this.editor.setText("");
|
|
2176
|
+
this.handleSkillsCommand();
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2094
2179
|
if (text === "/debug") {
|
|
2095
2180
|
this.handleDebugCommand();
|
|
2096
2181
|
this.editor.setText("");
|
|
@@ -3310,12 +3395,7 @@ export class InteractiveMode {
|
|
|
3310
3395
|
this.updateEditorBorderColor();
|
|
3311
3396
|
},
|
|
3312
3397
|
onThemeChange: (themeName) => {
|
|
3313
|
-
|
|
3314
|
-
this.settingsManager.setTheme(themeName);
|
|
3315
|
-
this.ui.invalidate();
|
|
3316
|
-
if (!result.success) {
|
|
3317
|
-
this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
|
|
3318
|
-
}
|
|
3398
|
+
this.applySettingsThemeChange(themeName);
|
|
3319
3399
|
},
|
|
3320
3400
|
onThemePreview: (themeName) => {
|
|
3321
3401
|
const result = setTheme(themeName, true);
|
|
@@ -3386,6 +3466,70 @@ export class InteractiveMode {
|
|
|
3386
3466
|
return { component: selector, focus: selector.getSettingsList() };
|
|
3387
3467
|
});
|
|
3388
3468
|
}
|
|
3469
|
+
applySettingsThemeChange(themeName) {
|
|
3470
|
+
const previousTheme = this.settingsManager.getTheme?.() || "dark";
|
|
3471
|
+
let applyResult = {
|
|
3472
|
+
success: false,
|
|
3473
|
+
error: "theme transaction did not run",
|
|
3474
|
+
};
|
|
3475
|
+
const transaction = runHarnessControlTransactionSync({
|
|
3476
|
+
kind: "interactive.theme.apply",
|
|
3477
|
+
data: { themeName, previousTheme, persistenceScope: "default" },
|
|
3478
|
+
beforeState: { theme: previousTheme },
|
|
3479
|
+
afterState: () => ({ theme: themeName }),
|
|
3480
|
+
commit: () => {
|
|
3481
|
+
const result = setTheme(themeName, true);
|
|
3482
|
+
applyResult = result;
|
|
3483
|
+
if (!result.success) {
|
|
3484
|
+
throw new Error(result.error ?? `Failed to load theme "${themeName}"`);
|
|
3485
|
+
}
|
|
3486
|
+
this.settingsManager.setTheme(themeName);
|
|
3487
|
+
this.ui.invalidate();
|
|
3488
|
+
return { theme: themeName };
|
|
3489
|
+
},
|
|
3490
|
+
rollback: () => {
|
|
3491
|
+
setTheme(previousTheme, true);
|
|
3492
|
+
},
|
|
3493
|
+
});
|
|
3494
|
+
if (transaction.status !== "completed") {
|
|
3495
|
+
this.showError(`Failed to load theme "${themeName}": ${applyResult.error}\nRestored theme "${previousTheme}".`);
|
|
3496
|
+
return applyResult;
|
|
3497
|
+
}
|
|
3498
|
+
return applyResult;
|
|
3499
|
+
}
|
|
3500
|
+
handleThinkCommand(level) {
|
|
3501
|
+
if (!level) {
|
|
3502
|
+
this.showThinkingSelector();
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
const availableLevels = this.session.getAvailableThinkingLevels();
|
|
3506
|
+
if (!availableLevels.includes(level)) {
|
|
3507
|
+
this.showError(`Thinking level "${level}" is not available. Available: ${availableLevels.join(", ")}`);
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
this.applyThinkingLevel(level);
|
|
3511
|
+
}
|
|
3512
|
+
applyThinkingLevel(level) {
|
|
3513
|
+
const previousThinkingLevel = this.session.thinkingLevel;
|
|
3514
|
+
const transaction = runHarnessControlTransactionSync({
|
|
3515
|
+
kind: "interactive.model_thinking.commit",
|
|
3516
|
+
data: { thinkingLevel: level, previousThinkingLevel, persistenceScope: "session" },
|
|
3517
|
+
beforeState: { thinkingLevel: previousThinkingLevel },
|
|
3518
|
+
afterState: () => ({ thinkingLevel: level }),
|
|
3519
|
+
commit: () => {
|
|
3520
|
+
this.session.setThinkingLevel(level);
|
|
3521
|
+
this.footer.invalidate();
|
|
3522
|
+
this.updateEditorBorderColor();
|
|
3523
|
+
return { thinkingLevel: level };
|
|
3524
|
+
},
|
|
3525
|
+
rollback: () => {
|
|
3526
|
+
this.session.setThinkingLevel(previousThinkingLevel);
|
|
3527
|
+
},
|
|
3528
|
+
});
|
|
3529
|
+
if (transaction.status === "completed") {
|
|
3530
|
+
this.showStatus(`Thinking: ${this.session.thinkingLevel}`);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3389
3533
|
async handleModelCommand(searchTerm) {
|
|
3390
3534
|
if (!searchTerm) {
|
|
3391
3535
|
this.showModelSelector();
|
|
@@ -3393,17 +3537,7 @@ export class InteractiveMode {
|
|
|
3393
3537
|
}
|
|
3394
3538
|
const model = await this.findExactModelMatch(searchTerm);
|
|
3395
3539
|
if (model) {
|
|
3396
|
-
|
|
3397
|
-
await this.session.setModel(model);
|
|
3398
|
-
this.footer.invalidate();
|
|
3399
|
-
this.updateEditorBorderColor();
|
|
3400
|
-
this.showStatus(`Model: ${model.id}`);
|
|
3401
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
3402
|
-
this.checkDaxnutsEasterEgg(model);
|
|
3403
|
-
}
|
|
3404
|
-
catch (error) {
|
|
3405
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3406
|
-
}
|
|
3540
|
+
this.showModelThinkingTransaction(model);
|
|
3407
3541
|
return;
|
|
3408
3542
|
}
|
|
3409
3543
|
this.showModelSelector(searchTerm);
|
|
@@ -3458,22 +3592,102 @@ export class InteractiveMode {
|
|
|
3458
3592
|
// Ignore auth lookup failures for warning-only checks.
|
|
3459
3593
|
}
|
|
3460
3594
|
}
|
|
3461
|
-
|
|
3595
|
+
showThinkingSelector() {
|
|
3462
3596
|
this.showSelector((done) => {
|
|
3463
|
-
const selector = new
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3597
|
+
const selector = new ThinkingSelectorComponent({
|
|
3598
|
+
thinkingLevel: this.session.thinkingLevel,
|
|
3599
|
+
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
|
3600
|
+
}, {
|
|
3601
|
+
onThinkingLevelChange: (level) => {
|
|
3602
|
+
this.applyThinkingLevel(level);
|
|
3603
|
+
},
|
|
3604
|
+
onSelectComplete: () => {
|
|
3468
3605
|
done();
|
|
3469
|
-
this.
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
}
|
|
3473
|
-
catch (error) {
|
|
3606
|
+
this.ui.requestRender();
|
|
3607
|
+
},
|
|
3608
|
+
onCancel: () => {
|
|
3474
3609
|
done();
|
|
3475
|
-
this.
|
|
3610
|
+
this.ui.requestRender();
|
|
3611
|
+
},
|
|
3612
|
+
});
|
|
3613
|
+
return { component: selector, focus: selector };
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
showModelThinkingTransaction(model) {
|
|
3617
|
+
const availableThinkingLevels = getSupportedThinkingLevels(model);
|
|
3618
|
+
const thinkingLevel = clampThinkingLevel(model, this.session.thinkingLevel);
|
|
3619
|
+
// Auto-skip selector when the model offers at most one thinking level (T04 routing safeguard).
|
|
3620
|
+
if (availableThinkingLevels.length <= 1) {
|
|
3621
|
+
const chosen = (availableThinkingLevels[0] ?? thinkingLevel ?? "off");
|
|
3622
|
+
void this.commitModelThinkingSelection(model, chosen);
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
this.showSelector((done) => {
|
|
3626
|
+
const selector = new ThinkingSelectorComponent({
|
|
3627
|
+
thinkingLevel,
|
|
3628
|
+
availableThinkingLevels,
|
|
3629
|
+
}, {
|
|
3630
|
+
onThinkingLevelChange: () => { },
|
|
3631
|
+
onSelectComplete: (level) => {
|
|
3632
|
+
done();
|
|
3633
|
+
void this.commitModelThinkingSelection(model, level);
|
|
3634
|
+
},
|
|
3635
|
+
onCancel: () => {
|
|
3636
|
+
done();
|
|
3637
|
+
this.ui.requestRender();
|
|
3638
|
+
},
|
|
3639
|
+
});
|
|
3640
|
+
return { component: selector, focus: selector };
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
async commitModelThinkingSelection(model, thinkingLevel) {
|
|
3644
|
+
const previousModel = this.session.model;
|
|
3645
|
+
const previousThinkingLevel = this.session.thinkingLevel;
|
|
3646
|
+
const transaction = await runHarnessControlTransaction({
|
|
3647
|
+
kind: "interactive.model_thinking.commit",
|
|
3648
|
+
data: {
|
|
3649
|
+
provider: model.provider,
|
|
3650
|
+
model: model.id,
|
|
3651
|
+
thinkingLevel,
|
|
3652
|
+
previousProvider: previousModel?.provider,
|
|
3653
|
+
previousModel: previousModel?.id,
|
|
3654
|
+
previousThinkingLevel,
|
|
3655
|
+
persistenceScope: "session",
|
|
3656
|
+
},
|
|
3657
|
+
beforeState: {
|
|
3658
|
+
provider: previousModel?.provider,
|
|
3659
|
+
model: previousModel?.id,
|
|
3660
|
+
thinkingLevel: previousThinkingLevel,
|
|
3661
|
+
},
|
|
3662
|
+
afterState: () => ({ provider: model.provider, model: model.id, thinkingLevel }),
|
|
3663
|
+
commit: async () => {
|
|
3664
|
+
await this.session.setModel(model);
|
|
3665
|
+
this.session.setThinkingLevel(thinkingLevel);
|
|
3666
|
+
this.footer.invalidate();
|
|
3667
|
+
this.updateEditorBorderColor();
|
|
3668
|
+
return { provider: model.provider, model: model.id, thinkingLevel };
|
|
3669
|
+
},
|
|
3670
|
+
rollback: async () => {
|
|
3671
|
+
if (previousModel && !modelsAreEqual(this.session.model, previousModel)) {
|
|
3672
|
+
await this.session.setModel(previousModel);
|
|
3476
3673
|
}
|
|
3674
|
+
this.session.setThinkingLevel(previousThinkingLevel);
|
|
3675
|
+
},
|
|
3676
|
+
});
|
|
3677
|
+
if (transaction.status === "completed") {
|
|
3678
|
+
this.showStatus(`Model: ${model.id} · Thinking: ${this.session.thinkingLevel}`);
|
|
3679
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
3680
|
+
this.checkDaxnutsEasterEgg(model);
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
const error = transaction.error ?? transaction.rollbackError ?? "Model transaction failed";
|
|
3684
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
3685
|
+
}
|
|
3686
|
+
showModelSelector(initialSearchInput) {
|
|
3687
|
+
this.showSelector((done) => {
|
|
3688
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, (model) => {
|
|
3689
|
+
done();
|
|
3690
|
+
this.showModelThinkingTransaction(model);
|
|
3477
3691
|
}, () => {
|
|
3478
3692
|
done();
|
|
3479
3693
|
this.ui.requestRender();
|
|
@@ -4405,6 +4619,35 @@ export class InteractiveMode {
|
|
|
4405
4619
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4406
4620
|
this.ui.requestRender();
|
|
4407
4621
|
}
|
|
4622
|
+
handleMcpCommand() {
|
|
4623
|
+
const inventory = loadMcpInventory(this.sessionManager.getCwd());
|
|
4624
|
+
this.showSelector((done) => {
|
|
4625
|
+
const selector = new McpSelectorComponent(inventory, () => {
|
|
4626
|
+
done();
|
|
4627
|
+
this.ui.requestRender();
|
|
4628
|
+
});
|
|
4629
|
+
return { component: selector, focus: selector };
|
|
4630
|
+
});
|
|
4631
|
+
const summary = inventory.entries.length === 1 ? "1 MCP server" : `${inventory.entries.length} MCP servers`;
|
|
4632
|
+
this.showStatus(`${summary} loaded (env values hidden)`);
|
|
4633
|
+
}
|
|
4634
|
+
handleSkillsCommand() {
|
|
4635
|
+
const result = this.session.resourceLoader.getSkills();
|
|
4636
|
+
const enableSkillCommands = this.settingsManager.getEnableSkillCommands();
|
|
4637
|
+
this.showSelector((done) => {
|
|
4638
|
+
const selector = new SkillsSelectorComponent({
|
|
4639
|
+
skills: result.skills,
|
|
4640
|
+
diagnostics: result.diagnostics,
|
|
4641
|
+
enableSkillCommands,
|
|
4642
|
+
}, () => {
|
|
4643
|
+
done();
|
|
4644
|
+
this.ui.requestRender();
|
|
4645
|
+
});
|
|
4646
|
+
return { component: selector, focus: selector };
|
|
4647
|
+
});
|
|
4648
|
+
const summary = result.skills.length === 1 ? "1 skill" : `${result.skills.length} skills`;
|
|
4649
|
+
this.showStatus(`${summary} loaded`);
|
|
4650
|
+
}
|
|
4408
4651
|
handleChangelogCommand() {
|
|
4409
4652
|
const changelogPath = getChangelogPath();
|
|
4410
4653
|
const allEntries = parseChangelog(changelogPath);
|