mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 01:00:24 +00:00
refactor(coding-agent): simplify InteractiveMode API
- Replace 11-param runInteractiveMode with InteractiveModeOptions - Add InteractiveMode.run() that handles init, warnings, prompts, and loop - Move version check, changelog loading, fdPath discovery inside InteractiveMode - Detect resumed sessions via session.state.messages.length - Export InteractiveModeOptions from modes/index.ts
This commit is contained in:
parent
cb3ac0ba9e
commit
5d39074a35
3 changed files with 162 additions and 141 deletions
|
|
@ -14,8 +14,6 @@ import { processFileArguments } from "./cli/file-processor.js";
|
||||||
import { listModels } from "./cli/list-models.js";
|
import { listModels } from "./cli/list-models.js";
|
||||||
import { selectSession } from "./cli/session-picker.js";
|
import { selectSession } from "./cli/session-picker.js";
|
||||||
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js";
|
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js";
|
||||||
import type { AgentSession } from "./core/agent-session.js";
|
|
||||||
|
|
||||||
import { createEventBus } from "./core/event-bus.js";
|
import { createEventBus } from "./core/event-bus.js";
|
||||||
import { exportFromFile } from "./core/export-html/index.js";
|
import { exportFromFile } from "./core/export-html/index.js";
|
||||||
import { discoverAndLoadExtensions, type LoadExtensionsResult } from "./core/extensions/index.js";
|
import { discoverAndLoadExtensions, type LoadExtensionsResult } from "./core/extensions/index.js";
|
||||||
|
|
@ -30,92 +28,6 @@ import { allTools } from "./core/tools/index.js";
|
||||||
import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
||||||
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
||||||
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
||||||
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
|
|
||||||
import { ensureTool } from "./utils/tools-manager.js";
|
|
||||||
|
|
||||||
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
||||||
try {
|
|
||||||
const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
|
|
||||||
if (!response.ok) return undefined;
|
|
||||||
|
|
||||||
const data = (await response.json()) as { version?: string };
|
|
||||||
const latestVersion = data.version;
|
|
||||||
|
|
||||||
if (latestVersion && latestVersion !== currentVersion) {
|
|
||||||
return latestVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runInteractiveMode(
|
|
||||||
session: AgentSession,
|
|
||||||
version: string,
|
|
||||||
changelogMarkdown: string | undefined,
|
|
||||||
modelFallbackMessage: string | undefined,
|
|
||||||
modelsJsonError: string | undefined,
|
|
||||||
migratedProviders: string[],
|
|
||||||
versionCheckPromise: Promise<string | undefined>,
|
|
||||||
initialMessages: string[],
|
|
||||||
initialMessage?: string,
|
|
||||||
initialImages?: ImageContent[],
|
|
||||||
fdPath: string | undefined = undefined,
|
|
||||||
): Promise<void> {
|
|
||||||
const mode = new InteractiveMode(session, version, changelogMarkdown, fdPath);
|
|
||||||
|
|
||||||
await mode.init();
|
|
||||||
|
|
||||||
versionCheckPromise.then((newVersion) => {
|
|
||||||
if (newVersion) {
|
|
||||||
mode.showNewVersionNotification(newVersion);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mode.renderInitialMessages();
|
|
||||||
|
|
||||||
if (migratedProviders.length > 0) {
|
|
||||||
mode.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modelsJsonError) {
|
|
||||||
mode.showError(`models.json error: ${modelsJsonError}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modelFallbackMessage) {
|
|
||||||
mode.showWarning(modelFallbackMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialMessage) {
|
|
||||||
try {
|
|
||||||
await session.prompt(initialMessage, { images: initialImages });
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
mode.showError(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const message of initialMessages) {
|
|
||||||
try {
|
|
||||||
await session.prompt(message);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
mode.showError(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const userInput = await mode.getUserInput();
|
|
||||||
try {
|
|
||||||
await session.prompt(userInput);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
mode.showError(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prepareInitialMessage(
|
async function prepareInitialMessage(
|
||||||
parsed: Args,
|
parsed: Args,
|
||||||
|
|
@ -144,31 +56,6 @@ async function prepareInitialMessage(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | undefined {
|
|
||||||
if (parsed.continue || parsed.resume) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastVersion = settingsManager.getLastChangelogVersion();
|
|
||||||
const changelogPath = getChangelogPath();
|
|
||||||
const entries = parseChangelog(changelogPath);
|
|
||||||
|
|
||||||
if (!lastVersion) {
|
|
||||||
if (entries.length > 0) {
|
|
||||||
settingsManager.setLastChangelogVersion(VERSION);
|
|
||||||
return entries.map((e) => e.content).join("\n\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const newEntries = getNewEntries(entries, lastVersion);
|
|
||||||
if (newEntries.length > 0) {
|
|
||||||
settingsManager.setLastChangelogVersion(VERSION);
|
|
||||||
return newEntries.map((e) => e.content).join("\n\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a session argument to a file path.
|
* Resolve a session argument to a file path.
|
||||||
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
||||||
|
|
@ -473,9 +360,6 @@ export async function main(args: string[]) {
|
||||||
if (mode === "rpc") {
|
if (mode === "rpc") {
|
||||||
await runRpcMode(session);
|
await runRpcMode(session);
|
||||||
} else if (isInteractive) {
|
} else if (isInteractive) {
|
||||||
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
||||||
const changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);
|
|
||||||
|
|
||||||
if (scopedModels.length > 0) {
|
if (scopedModels.length > 0) {
|
||||||
const modelList = scopedModels
|
const modelList = scopedModels
|
||||||
.map((sm) => {
|
.map((sm) => {
|
||||||
|
|
@ -486,23 +370,15 @@ export async function main(args: string[]) {
|
||||||
console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
|
console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fdPath = await ensureTool("fd");
|
|
||||||
time("ensureTool(fd)");
|
|
||||||
|
|
||||||
printTimings();
|
printTimings();
|
||||||
await runInteractiveMode(
|
const mode = new InteractiveMode(session, {
|
||||||
session,
|
|
||||||
VERSION,
|
|
||||||
changelogMarkdown,
|
|
||||||
modelFallbackMessage,
|
|
||||||
modelRegistry.getError(),
|
|
||||||
migratedProviders,
|
migratedProviders,
|
||||||
versionCheckPromise,
|
modelFallbackMessage,
|
||||||
parsed.messages,
|
|
||||||
initialMessage,
|
initialMessage,
|
||||||
initialImages,
|
initialImages,
|
||||||
fdPath,
|
initialMessages: parsed.messages,
|
||||||
);
|
});
|
||||||
|
await mode.run();
|
||||||
} else {
|
} else {
|
||||||
await runPrintMode(session, mode, parsed.messages, initialMessage, initialImages);
|
await runPrintMode(session, mode, parsed.messages, initialMessage, initialImages);
|
||||||
stopThemeWatcher();
|
stopThemeWatcher();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* Run modes for the coding agent.
|
* Run modes for the coding agent.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { InteractiveMode } from "./interactive/interactive-mode.js";
|
export { InteractiveMode, type InteractiveModeOptions } from "./interactive/interactive-mode.js";
|
||||||
export { runPrintMode } from "./print-mode.js";
|
export { runPrintMode } from "./print-mode.js";
|
||||||
export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client.js";
|
export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client.js";
|
||||||
export { runRpcMode } from "./rpc/rpc-mode.js";
|
export { runRpcMode } from "./rpc/rpc-mode.js";
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import * as fs from "node:fs";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import { type AssistantMessage, getOAuthProviders, type Message, type OAuthProvider } from "@mariozechner/pi-ai";
|
import {
|
||||||
|
type AssistantMessage,
|
||||||
|
getOAuthProviders,
|
||||||
|
type ImageContent,
|
||||||
|
type Message,
|
||||||
|
type OAuthProvider,
|
||||||
|
} from "@mariozechner/pi-ai";
|
||||||
import type { EditorComponent, EditorTheme, KeyId, SlashCommand } from "@mariozechner/pi-tui";
|
import type { EditorComponent, EditorTheme, KeyId, SlashCommand } from "@mariozechner/pi-tui";
|
||||||
import {
|
import {
|
||||||
CombinedAutocompleteProvider,
|
CombinedAutocompleteProvider,
|
||||||
|
|
@ -26,7 +32,7 @@ import {
|
||||||
visibleWidth,
|
visibleWidth,
|
||||||
} from "@mariozechner/pi-tui";
|
} from "@mariozechner/pi-tui";
|
||||||
import { spawn, spawnSync } from "child_process";
|
import { spawn, spawnSync } from "child_process";
|
||||||
import { APP_NAME, getAuthPath, getDebugLogPath } from "../../config.js";
|
import { APP_NAME, getAuthPath, getDebugLogPath, VERSION } from "../../config.js";
|
||||||
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session.js";
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session.js";
|
||||||
import type {
|
import type {
|
||||||
ExtensionContext,
|
ExtensionContext,
|
||||||
|
|
@ -41,9 +47,10 @@ import { loadSkills } from "../../core/skills.js";
|
||||||
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
||||||
import { allTools } from "../../core/tools/index.js";
|
import { allTools } from "../../core/tools/index.js";
|
||||||
import type { TruncationResult } from "../../core/tools/truncate.js";
|
import type { TruncationResult } from "../../core/tools/truncate.js";
|
||||||
import { getChangelogPath, parseChangelog } from "../../utils/changelog.js";
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
||||||
import { copyToClipboard } from "../../utils/clipboard.js";
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
||||||
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
||||||
|
import { ensureTool } from "../../utils/tools-manager.js";
|
||||||
import { ArminComponent } from "./components/armin.js";
|
import { ArminComponent } from "./components/armin.js";
|
||||||
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
||||||
import { BashExecutionComponent } from "./components/bash-execution.js";
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
||||||
|
|
@ -90,6 +97,22 @@ type CompactionQueuedMessage = {
|
||||||
mode: "steer" | "followUp";
|
mode: "steer" | "followUp";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for InteractiveMode initialization.
|
||||||
|
*/
|
||||||
|
export interface InteractiveModeOptions {
|
||||||
|
/** Providers that were migrated to auth.json (shows warning) */
|
||||||
|
migratedProviders?: string[];
|
||||||
|
/** Warning message if session model couldn't be restored */
|
||||||
|
modelFallbackMessage?: string;
|
||||||
|
/** Initial message to send on startup (can include @file content) */
|
||||||
|
initialMessage?: string;
|
||||||
|
/** Images to attach to the initial message */
|
||||||
|
initialImages?: ImageContent[];
|
||||||
|
/** Additional messages to send after the initial message */
|
||||||
|
initialMessages?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class InteractiveMode {
|
export class InteractiveMode {
|
||||||
private session: AgentSession;
|
private session: AgentSession;
|
||||||
private ui: TUI;
|
private ui: TUI;
|
||||||
|
|
@ -182,13 +205,10 @@ export class InteractiveMode {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
session: AgentSession,
|
session: AgentSession,
|
||||||
version: string,
|
private options: InteractiveModeOptions = {},
|
||||||
changelogMarkdown: string | undefined = undefined,
|
|
||||||
fdPath: string | undefined = undefined,
|
|
||||||
) {
|
) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.version = version;
|
this.version = VERSION;
|
||||||
this.changelogMarkdown = changelogMarkdown;
|
|
||||||
this.ui = new TUI(new ProcessTerminal());
|
this.ui = new TUI(new ProcessTerminal());
|
||||||
this.chatContainer = new Container();
|
this.chatContainer = new Container();
|
||||||
this.pendingMessagesContainer = new Container();
|
this.pendingMessagesContainer = new Container();
|
||||||
|
|
@ -202,6 +222,11 @@ export class InteractiveMode {
|
||||||
this.footer = new FooterComponent(session);
|
this.footer = new FooterComponent(session);
|
||||||
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
||||||
|
|
||||||
|
// Load hide thinking block setting
|
||||||
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupAutocomplete(fdPath: string | undefined): void {
|
||||||
// Define commands for autocomplete
|
// Define commands for autocomplete
|
||||||
const slashCommands: SlashCommand[] = [
|
const slashCommands: SlashCommand[] = [
|
||||||
{ name: "settings", description: "Open settings menu" },
|
{ name: "settings", description: "Open settings menu" },
|
||||||
|
|
@ -221,9 +246,6 @@ export class InteractiveMode {
|
||||||
{ name: "resume", description: "Resume a different session" },
|
{ name: "resume", description: "Resume a different session" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Load hide thinking block setting
|
|
||||||
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
||||||
|
|
||||||
// Convert prompt templates to SlashCommand format for autocomplete
|
// Convert prompt templates to SlashCommand format for autocomplete
|
||||||
const templateCommands: SlashCommand[] = this.session.promptTemplates.map((cmd) => ({
|
const templateCommands: SlashCommand[] = this.session.promptTemplates.map((cmd) => ({
|
||||||
name: cmd.name,
|
name: cmd.name,
|
||||||
|
|
@ -250,6 +272,13 @@ export class InteractiveMode {
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
if (this.isInitialized) return;
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
// Load changelog (only show new entries, skip for resumed sessions)
|
||||||
|
this.changelogMarkdown = this.getChangelogForDisplay();
|
||||||
|
|
||||||
|
// Setup autocomplete with fd tool for file path completion
|
||||||
|
const fdPath = await ensureTool("fd");
|
||||||
|
this.setupAutocomplete(fdPath);
|
||||||
|
|
||||||
// Add header with keybindings from config
|
// Add header with keybindings from config
|
||||||
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
||||||
|
|
||||||
|
|
@ -391,6 +420,122 @@ export class InteractiveMode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the interactive mode. This is the main entry point.
|
||||||
|
* Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.
|
||||||
|
*/
|
||||||
|
async run(): Promise<void> {
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
// Start version check asynchronously
|
||||||
|
this.checkForNewVersion().then((newVersion) => {
|
||||||
|
if (newVersion) {
|
||||||
|
this.showNewVersionNotification(newVersion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderInitialMessages();
|
||||||
|
|
||||||
|
// Show startup warnings
|
||||||
|
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
|
||||||
|
|
||||||
|
if (migratedProviders && migratedProviders.length > 0) {
|
||||||
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelsJsonError = this.session.modelRegistry.getError();
|
||||||
|
if (modelsJsonError) {
|
||||||
|
this.showError(`models.json error: ${modelsJsonError}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelFallbackMessage) {
|
||||||
|
this.showWarning(modelFallbackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process initial messages
|
||||||
|
if (initialMessage) {
|
||||||
|
try {
|
||||||
|
await this.session.prompt(initialMessage, { images: initialImages });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
|
this.showError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialMessages) {
|
||||||
|
for (const message of initialMessages) {
|
||||||
|
try {
|
||||||
|
await this.session.prompt(message);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
|
this.showError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main interactive loop
|
||||||
|
while (true) {
|
||||||
|
const userInput = await this.getUserInput();
|
||||||
|
try {
|
||||||
|
await this.session.prompt(userInput);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
|
this.showError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check npm registry for a newer version.
|
||||||
|
*/
|
||||||
|
private async checkForNewVersion(): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
|
||||||
|
if (!response.ok) return undefined;
|
||||||
|
|
||||||
|
const data = (await response.json()) as { version?: string };
|
||||||
|
const latestVersion = data.version;
|
||||||
|
|
||||||
|
if (latestVersion && latestVersion !== this.version) {
|
||||||
|
return latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get changelog entries to display on startup.
|
||||||
|
* Only shows new entries since last seen version, skips for resumed sessions.
|
||||||
|
*/
|
||||||
|
private getChangelogForDisplay(): string | undefined {
|
||||||
|
// Skip changelog for resumed/continued sessions (already have messages)
|
||||||
|
if (this.session.state.messages.length > 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastVersion = this.settingsManager.getLastChangelogVersion();
|
||||||
|
const changelogPath = getChangelogPath();
|
||||||
|
const entries = parseChangelog(changelogPath);
|
||||||
|
|
||||||
|
if (!lastVersion) {
|
||||||
|
if (entries.length > 0) {
|
||||||
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||||
|
return entries.map((e) => e.content).join("\n\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newEntries = getNewEntries(entries, lastVersion);
|
||||||
|
if (newEntries.length > 0) {
|
||||||
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||||
|
return newEntries.map((e) => e.content).join("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Extension System
|
// Extension System
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue