@f5xc-salesdemos/xcsh 17.4.1 → 17.4.2
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/package.json +7 -7
- package/src/config/auto-config.ts +23 -67
- package/src/config/settings-schema.ts +0 -6
- package/src/main.ts +30 -17
- package/src/modes/components/welcome.ts +65 -1
- package/src/modes/interactive-mode.ts +18 -23
- package/src/modes/types.ts +0 -1
- package/src/modes/utils/ui-helpers.ts +0 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "17.4.
|
|
4
|
+
"version": "17.4.2",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
48
48
|
"@mozilla/readability": "^0.6",
|
|
49
|
-
"@f5xc-salesdemos/xcsh-stats": "17.4.
|
|
50
|
-
"@f5xc-salesdemos/pi-agent-core": "17.4.
|
|
51
|
-
"@f5xc-salesdemos/pi-ai": "17.4.
|
|
52
|
-
"@f5xc-salesdemos/pi-natives": "17.4.
|
|
53
|
-
"@f5xc-salesdemos/pi-tui": "17.4.
|
|
54
|
-
"@f5xc-salesdemos/pi-utils": "17.4.
|
|
49
|
+
"@f5xc-salesdemos/xcsh-stats": "17.4.2",
|
|
50
|
+
"@f5xc-salesdemos/pi-agent-core": "17.4.2",
|
|
51
|
+
"@f5xc-salesdemos/pi-ai": "17.4.2",
|
|
52
|
+
"@f5xc-salesdemos/pi-natives": "17.4.2",
|
|
53
|
+
"@f5xc-salesdemos/pi-tui": "17.4.2",
|
|
54
|
+
"@f5xc-salesdemos/pi-utils": "17.4.2",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
-
import { $env, logger } from "@f5xc-salesdemos/pi-utils";
|
|
18
|
+
import { $env, logger, readProviderFromModelsYml } from "@f5xc-salesdemos/pi-utils";
|
|
19
19
|
|
|
20
20
|
/** Current config schema version. Bump when the generated format changes. */
|
|
21
21
|
export const CURRENT_CONFIG_VERSION = 2;
|
|
@@ -89,60 +89,28 @@ export interface LiteLLMConfig {
|
|
|
89
89
|
* Read existing models.yml and extract the anthropic provider's base URL and API key.
|
|
90
90
|
*
|
|
91
91
|
* - baseUrl: strips `/anthropic` suffix to recover the root proxy URL
|
|
92
|
-
* - apiKey:
|
|
93
|
-
*
|
|
94
|
-
* - Falls back to getLiteLLMBaseUrl()
|
|
95
|
-
*
|
|
92
|
+
* - apiKey: env var name → process.env lookup; shell-backed → fall back to env;
|
|
93
|
+
* otherwise literal
|
|
94
|
+
* - Falls back to getLiteLLMBaseUrl() and $env.LITELLM_API_KEY when the file is
|
|
95
|
+
* missing or the anthropic block is incomplete
|
|
96
96
|
*/
|
|
97
97
|
export function readLiteLLMConfig(modelsPath: string): LiteLLMConfig | undefined {
|
|
98
98
|
if (!fs.existsSync(modelsPath)) return undefined;
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
let rawApiKey: string | undefined;
|
|
100
|
+
const block = readProviderFromModelsYml("anthropic", modelsPath);
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
const content = fs.readFileSync(modelsPath, "utf8");
|
|
105
|
-
const lines = content.split("\n");
|
|
106
|
-
let inAnthropicBlock = false;
|
|
102
|
+
const resolvedBaseUrl = block?.baseUrl ? block.baseUrl.replace(/\/anthropic\/?$/, "") : getLiteLLMBaseUrl();
|
|
107
103
|
|
|
108
|
-
for (const line of lines) {
|
|
109
|
-
// Detect entry into anthropic provider block
|
|
110
|
-
if (/^\s{2}anthropic\s*:/.test(line)) {
|
|
111
|
-
inAnthropicBlock = true;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
// Detect leaving the block: a new sibling provider key (2-space indent, non-empty)
|
|
115
|
-
if (inAnthropicBlock && /^\s{2}\S/.test(line)) {
|
|
116
|
-
inAnthropicBlock = false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (inAnthropicBlock) {
|
|
120
|
-
const baseUrlMatch = line.match(/^\s+baseUrl\s*:\s*"?([^"]+)"?\s*$/);
|
|
121
|
-
if (baseUrlMatch) {
|
|
122
|
-
rawBaseUrl = baseUrlMatch[1].trim();
|
|
123
|
-
}
|
|
124
|
-
const apiKeyMatch = line.match(/^\s+apiKey\s*:\s*(.+)\s*$/);
|
|
125
|
-
if (apiKeyMatch) {
|
|
126
|
-
rawApiKey = apiKeyMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Resolve base URL: strip /anthropic suffix
|
|
132
|
-
const resolvedBaseUrl = rawBaseUrl ? rawBaseUrl.replace(/\/anthropic\/?$/, "") : getLiteLLMBaseUrl();
|
|
133
|
-
|
|
134
|
-
// Resolve API key: env var name → process.env lookup; command-backed → skip; otherwise literal
|
|
135
104
|
let resolvedApiKey: string | undefined;
|
|
136
|
-
|
|
105
|
+
const apiKey = block?.apiKey;
|
|
106
|
+
if (!apiKey) {
|
|
137
107
|
resolvedApiKey = $env.LITELLM_API_KEY;
|
|
138
|
-
} else if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
} else if (rawApiKey.startsWith("!")) {
|
|
142
|
-
// Command-backed secrets (e.g. "!command ...") can't be resolved here — fall back to env
|
|
108
|
+
} else if (apiKey.kind === "envVar") {
|
|
109
|
+
resolvedApiKey = process.env[apiKey.name] ?? $env.LITELLM_API_KEY;
|
|
110
|
+
} else if (apiKey.kind === "shellSecret") {
|
|
143
111
|
resolvedApiKey = $env.LITELLM_API_KEY;
|
|
144
112
|
} else {
|
|
145
|
-
resolvedApiKey =
|
|
113
|
+
resolvedApiKey = apiKey.value;
|
|
146
114
|
}
|
|
147
115
|
|
|
148
116
|
if (!resolvedBaseUrl || !resolvedApiKey) return undefined;
|
|
@@ -159,6 +127,7 @@ export function generateConfigYml(): string {
|
|
|
159
127
|
"",
|
|
160
128
|
"providers:",
|
|
161
129
|
" image: openai",
|
|
130
|
+
" webSearch: anthropic",
|
|
162
131
|
"",
|
|
163
132
|
"generate_image:",
|
|
164
133
|
" enabled: true",
|
|
@@ -200,31 +169,18 @@ export function healConfigYmlModelRoles(configPath: string): void {
|
|
|
200
169
|
// ---------------------------------------------------------------------------
|
|
201
170
|
|
|
202
171
|
/**
|
|
203
|
-
* Extract a quoted literal API key from an existing models.yml file.
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
172
|
+
* Extract a double-quoted literal API key from an existing models.yml file.
|
|
173
|
+
* Checks the anthropic and litellm provider blocks; returns undefined if
|
|
174
|
+
* neither has a literal (e.g., both use env var references) or the file is
|
|
175
|
+
* missing.
|
|
207
176
|
*/
|
|
208
177
|
export function readApiKeyLiteral(modelsPath: string): string | undefined {
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
for (const line of lines) {
|
|
215
|
-
if (/^\s{2}(?:anthropic|litellm)\s*:/.test(line)) {
|
|
216
|
-
inTargetBlock = true;
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (inTargetBlock && /^\s{2}\S/.test(line)) {
|
|
220
|
-
inTargetBlock = false;
|
|
221
|
-
}
|
|
222
|
-
if (inTargetBlock) {
|
|
223
|
-
const match = line.match(/^\s+apiKey:\s*"([^"]+)"/);
|
|
224
|
-
if (match) return match[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
225
|
-
}
|
|
178
|
+
for (const name of ["anthropic", "litellm"]) {
|
|
179
|
+
const block = readProviderFromModelsYml(name, modelsPath);
|
|
180
|
+
if (block?.apiKey?.kind === "literal" && block.apiKey.wasQuoted) {
|
|
181
|
+
return block.apiKey.value;
|
|
226
182
|
}
|
|
227
|
-
}
|
|
183
|
+
}
|
|
228
184
|
return undefined;
|
|
229
185
|
}
|
|
230
186
|
|
|
@@ -662,12 +662,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
662
662
|
},
|
|
663
663
|
},
|
|
664
664
|
|
|
665
|
-
collapseChangelog: {
|
|
666
|
-
type: "boolean",
|
|
667
|
-
default: true,
|
|
668
|
-
ui: { tab: "interaction", label: "Collapse Changelog", description: "Show condensed changelog after updates" },
|
|
669
|
-
},
|
|
670
|
-
|
|
671
665
|
// Notifications
|
|
672
666
|
"completion.notify": {
|
|
673
667
|
type: "enum",
|
package/src/main.ts
CHANGED
|
@@ -144,10 +144,12 @@ export async function submitInteractiveInput(
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
const INITIAL_UPDATE_CHECK_TIMEOUT_MS = 500;
|
|
148
|
+
|
|
147
149
|
async function runInteractiveMode(
|
|
148
150
|
session: AgentSession,
|
|
149
151
|
version: string,
|
|
150
|
-
|
|
152
|
+
changelogStatus: { hasNew: boolean; version: string } | undefined,
|
|
151
153
|
notifs: (InteractiveModeNotify | null)[],
|
|
152
154
|
versionCheckPromise: Promise<string | undefined>,
|
|
153
155
|
initialMessages: string[],
|
|
@@ -158,10 +160,19 @@ async function runInteractiveMode(
|
|
|
158
160
|
initialMessage?: string,
|
|
159
161
|
initialImages?: ImageContent[],
|
|
160
162
|
): Promise<void> {
|
|
163
|
+
const initialUpdateVersion = await Promise.race([
|
|
164
|
+
versionCheckPromise.catch(() => undefined),
|
|
165
|
+
new Promise<string | undefined>(resolve => setTimeout(() => resolve(undefined), INITIAL_UPDATE_CHECK_TIMEOUT_MS)),
|
|
166
|
+
]);
|
|
167
|
+
const initialUpdateStatus = initialUpdateVersion
|
|
168
|
+
? { available: true, latestVersion: initialUpdateVersion }
|
|
169
|
+
: undefined;
|
|
170
|
+
|
|
161
171
|
const mode = new InteractiveMode(
|
|
162
172
|
session,
|
|
163
173
|
version,
|
|
164
|
-
|
|
174
|
+
changelogStatus,
|
|
175
|
+
initialUpdateStatus,
|
|
165
176
|
setExtensionUIContext,
|
|
166
177
|
lspServers,
|
|
167
178
|
mcpManager,
|
|
@@ -170,16 +181,18 @@ async function runInteractiveMode(
|
|
|
170
181
|
|
|
171
182
|
await mode.init();
|
|
172
183
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
if (!initialUpdateVersion) {
|
|
185
|
+
versionCheckPromise
|
|
186
|
+
.then(newVersion => {
|
|
187
|
+
if (!settings.get("startup.checkUpdate")) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (newVersion) {
|
|
191
|
+
mode.setUpdateStatus({ available: true, latestVersion: newVersion });
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.catch(() => {});
|
|
195
|
+
}
|
|
183
196
|
|
|
184
197
|
mode.renderInitialMessages();
|
|
185
198
|
|
|
@@ -243,7 +256,7 @@ async function promptForkSession(session: SessionInfo): Promise<boolean> {
|
|
|
243
256
|
}
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
async function getChangelogForDisplay(parsed: Args): Promise<string | undefined> {
|
|
259
|
+
async function getChangelogForDisplay(parsed: Args): Promise<{ hasNew: boolean; version: string } | undefined> {
|
|
247
260
|
if (parsed.continue || parsed.resume) {
|
|
248
261
|
return undefined;
|
|
249
262
|
}
|
|
@@ -255,13 +268,13 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
|
|
|
255
268
|
if (!lastVersion) {
|
|
256
269
|
if (entries.length > 0) {
|
|
257
270
|
settings.set("lastChangelogVersion", VERSION);
|
|
258
|
-
return
|
|
271
|
+
return { hasNew: true, version: VERSION };
|
|
259
272
|
}
|
|
260
273
|
} else {
|
|
261
274
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
262
275
|
if (newEntries.length > 0) {
|
|
263
276
|
settings.set("lastChangelogVersion", VERSION);
|
|
264
|
-
return
|
|
277
|
+
return { hasNew: true, version: VERSION };
|
|
265
278
|
}
|
|
266
279
|
}
|
|
267
280
|
|
|
@@ -877,7 +890,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
877
890
|
} else if (isInteractive) {
|
|
878
891
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
879
892
|
logger.time("main:getChangelogForDisplay");
|
|
880
|
-
const
|
|
893
|
+
const changelogStatus = await getChangelogForDisplay(parsedArgs);
|
|
881
894
|
|
|
882
895
|
const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels;
|
|
883
896
|
if (scopedModelsForDisplay.length > 0) {
|
|
@@ -901,7 +914,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
901
914
|
await runInteractiveMode(
|
|
902
915
|
session,
|
|
903
916
|
VERSION,
|
|
904
|
-
|
|
917
|
+
changelogStatus,
|
|
905
918
|
notifs,
|
|
906
919
|
versionCheckPromise,
|
|
907
920
|
parsedArgs.messages,
|
|
@@ -3,11 +3,23 @@ import { APP_NAME } from "@f5xc-salesdemos/pi-utils";
|
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
import type { ModelStatus, WelcomeProfileStatus } from "./welcome-checks";
|
|
5
5
|
|
|
6
|
+
export interface UpdateStatus {
|
|
7
|
+
available: boolean;
|
|
8
|
+
latestVersion?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ChangelogStatus {
|
|
12
|
+
hasNew: boolean;
|
|
13
|
+
version: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
export class WelcomeComponent implements Component {
|
|
7
17
|
constructor(
|
|
8
18
|
private readonly version: string,
|
|
9
19
|
private modelStatus: ModelStatus,
|
|
10
20
|
private profileStatus?: WelcomeProfileStatus,
|
|
21
|
+
private updateStatus?: UpdateStatus,
|
|
22
|
+
private changelogStatus?: ChangelogStatus,
|
|
11
23
|
) {}
|
|
12
24
|
invalidate(): void {}
|
|
13
25
|
setModelStatus(status: ModelStatus): void {
|
|
@@ -16,6 +28,12 @@ export class WelcomeComponent implements Component {
|
|
|
16
28
|
setProfileStatus(status: WelcomeProfileStatus | undefined): void {
|
|
17
29
|
this.profileStatus = status;
|
|
18
30
|
}
|
|
31
|
+
setUpdateStatus(status: UpdateStatus | undefined): void {
|
|
32
|
+
this.updateStatus = status;
|
|
33
|
+
}
|
|
34
|
+
setChangelogStatus(status: ChangelogStatus | undefined): void {
|
|
35
|
+
this.changelogStatus = status;
|
|
36
|
+
}
|
|
19
37
|
|
|
20
38
|
render(termWidth: number): string[] {
|
|
21
39
|
const minLeftCol = 48;
|
|
@@ -111,26 +129,72 @@ export class WelcomeComponent implements Component {
|
|
|
111
129
|
if (this.profileStatus) {
|
|
112
130
|
lines.push(" F5 XC Profile", ...this.#renderProfileStatus());
|
|
113
131
|
}
|
|
132
|
+
if (this.#showUpdateSection()) {
|
|
133
|
+
lines.push(" Update Available", ...this.#renderUpdateStatus());
|
|
134
|
+
}
|
|
135
|
+
if (this.#showChangelogSection()) {
|
|
136
|
+
lines.push(" What's New", ...this.#renderChangelogStatus());
|
|
137
|
+
}
|
|
114
138
|
return Math.max(...lines.map(l => visibleWidth(l)));
|
|
115
139
|
}
|
|
116
140
|
|
|
117
141
|
#buildStatusLines(rightCol: number): string[] {
|
|
118
142
|
const lines: string[] = [];
|
|
119
143
|
const separatorWidth = Math.max(0, rightCol - 2);
|
|
144
|
+
const separator = ` ${theme.fg("muted", theme.boxRound.horizontal.repeat(separatorWidth))}`;
|
|
120
145
|
lines.push("");
|
|
121
146
|
lines.push(` ${theme.bold(theme.fg("contentAccent", "Model Provider"))}`);
|
|
122
147
|
lines.push(...this.#renderModelStatus());
|
|
123
148
|
lines.push("");
|
|
124
149
|
if (this.profileStatus) {
|
|
125
|
-
lines.push(
|
|
150
|
+
lines.push(separator);
|
|
126
151
|
lines.push("");
|
|
127
152
|
lines.push(` ${theme.bold(theme.fg("contentAccent", "F5 XC Profile"))}`);
|
|
128
153
|
lines.push(...this.#renderProfileStatus());
|
|
129
154
|
lines.push("");
|
|
130
155
|
}
|
|
156
|
+
if (this.#showUpdateSection()) {
|
|
157
|
+
lines.push(separator);
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push(` ${theme.bold(theme.fg("contentAccent", "Update Available"))}`);
|
|
160
|
+
lines.push(...this.#renderUpdateStatus());
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
163
|
+
if (this.#showChangelogSection()) {
|
|
164
|
+
lines.push(separator);
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(` ${theme.bold(theme.fg("contentAccent", "What's New"))}`);
|
|
167
|
+
lines.push(...this.#renderChangelogStatus());
|
|
168
|
+
lines.push("");
|
|
169
|
+
}
|
|
131
170
|
return lines;
|
|
132
171
|
}
|
|
133
172
|
|
|
173
|
+
#showUpdateSection(): boolean {
|
|
174
|
+
return this.updateStatus?.available === true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#showChangelogSection(): boolean {
|
|
178
|
+
return this.changelogStatus?.hasNew === true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#renderUpdateStatus(): string[] {
|
|
182
|
+
const latest = this.updateStatus?.latestVersion;
|
|
183
|
+
const label = latest ? `v${latest}` : "new version";
|
|
184
|
+
return [
|
|
185
|
+
` ${theme.fg("warning", "\u2191")} ${theme.fg("muted", label)}`,
|
|
186
|
+
` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "xcsh update")}`,
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#renderChangelogStatus(): string[] {
|
|
191
|
+
const v = this.changelogStatus?.version ?? this.version;
|
|
192
|
+
return [
|
|
193
|
+
` ${theme.fg("success", "\u2605")} ${theme.fg("muted", `v${v}`)}`,
|
|
194
|
+
` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "/changelog")}`,
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
134
198
|
#renderModelStatus(): string[] {
|
|
135
199
|
const { state, provider, latencyMs } = this.modelStatus;
|
|
136
200
|
const p = provider ?? "unknown";
|
|
@@ -49,7 +49,7 @@ import type { HookSelectorComponent } from "./components/hook-selector";
|
|
|
49
49
|
import type { PythonExecutionComponent } from "./components/python-execution";
|
|
50
50
|
import { StatusLineComponent } from "./components/status-line";
|
|
51
51
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
52
|
-
import { WelcomeComponent } from "./components/welcome";
|
|
52
|
+
import { type ChangelogStatus, type UpdateStatus, WelcomeComponent } from "./components/welcome";
|
|
53
53
|
import { runWelcomeChecks } from "./components/welcome-checks";
|
|
54
54
|
import { BtwController } from "./controllers/btw-controller";
|
|
55
55
|
import { CommandController } from "./controllers/command-controller";
|
|
@@ -161,7 +161,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
161
161
|
#pendingSlashCommands: SlashCommand[] = [];
|
|
162
162
|
#cleanupUnsubscribe?: () => void;
|
|
163
163
|
readonly #version: string;
|
|
164
|
-
readonly #
|
|
164
|
+
readonly #changelogStatus: ChangelogStatus | undefined;
|
|
165
|
+
readonly #initialUpdateStatus: UpdateStatus | undefined;
|
|
165
166
|
#planModePreviousTools: string[] | undefined;
|
|
166
167
|
#planModePreviousModelState: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
|
|
167
168
|
#pendingModelSwitch: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
|
|
@@ -192,7 +193,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
192
193
|
constructor(
|
|
193
194
|
session: AgentSession,
|
|
194
195
|
version: string,
|
|
195
|
-
|
|
196
|
+
changelogStatus: ChangelogStatus | undefined = undefined,
|
|
197
|
+
initialUpdateStatus: UpdateStatus | undefined = undefined,
|
|
196
198
|
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
|
|
197
199
|
lspServers?: import("../tools").LspStartupServerInfo[],
|
|
198
200
|
mcpManager?: import("../mcp").MCPManager,
|
|
@@ -204,7 +206,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
204
206
|
this.keybindings = KeybindingsManager.inMemory();
|
|
205
207
|
this.agent = session.agent;
|
|
206
208
|
this.#version = version;
|
|
207
|
-
this.#
|
|
209
|
+
this.#changelogStatus = changelogStatus;
|
|
210
|
+
this.#initialUpdateStatus = initialUpdateStatus;
|
|
208
211
|
this.#toolUiContextSetter = setToolUIContext;
|
|
209
212
|
this.lspServers = lspServers;
|
|
210
213
|
this.mcpManager = mcpManager;
|
|
@@ -321,28 +324,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
if (!startupQuiet) {
|
|
324
|
-
//
|
|
325
|
-
this.#welcomeComponent = new WelcomeComponent(
|
|
327
|
+
// Welcome box owns all startup notifications (model, profile, update, changelog)
|
|
328
|
+
this.#welcomeComponent = new WelcomeComponent(
|
|
329
|
+
this.#version,
|
|
330
|
+
welcomeResult.model,
|
|
331
|
+
welcomeResult.profile,
|
|
332
|
+
this.#initialUpdateStatus,
|
|
333
|
+
this.#changelogStatus,
|
|
334
|
+
);
|
|
326
335
|
|
|
327
336
|
// Setup UI layout
|
|
328
337
|
this.ui.addChild(new Spacer(1));
|
|
329
338
|
this.ui.addChild(this.#welcomeComponent);
|
|
330
339
|
this.ui.addChild(new Spacer(1));
|
|
331
|
-
|
|
332
|
-
// Add changelog if provided
|
|
333
|
-
if (this.#changelogMarkdown) {
|
|
334
|
-
this.ui.addChild(new DynamicBorder());
|
|
335
|
-
if (settings.get("collapseChangelog")) {
|
|
336
|
-
const condensedText = `Updated to v${this.#version}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
337
|
-
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
338
|
-
} else {
|
|
339
|
-
this.ui.addChild(new Text(theme.bold(theme.fg("contentAccent", "What's New")), 1, 0));
|
|
340
|
-
this.ui.addChild(new Spacer(1));
|
|
341
|
-
this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
342
|
-
this.ui.addChild(new Spacer(1));
|
|
343
|
-
}
|
|
344
|
-
this.ui.addChild(new DynamicBorder());
|
|
345
|
-
}
|
|
346
340
|
}
|
|
347
341
|
|
|
348
342
|
this.ui.addChild(this.chatContainer);
|
|
@@ -1130,8 +1124,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1130
1124
|
this.setWorkingMessage(message);
|
|
1131
1125
|
}
|
|
1132
1126
|
|
|
1133
|
-
|
|
1134
|
-
this.#
|
|
1127
|
+
setUpdateStatus(status: UpdateStatus | undefined): void {
|
|
1128
|
+
this.#welcomeComponent?.setUpdateStatus(status);
|
|
1129
|
+
this.ui.requestRender();
|
|
1135
1130
|
}
|
|
1136
1131
|
|
|
1137
1132
|
clearEditor(): void {
|
package/src/modes/types.ts
CHANGED
|
@@ -137,7 +137,6 @@ export interface InteractiveModeContext {
|
|
|
137
137
|
showStatus(message: string, options?: { dim?: boolean }): void;
|
|
138
138
|
showError(message: string): void;
|
|
139
139
|
showWarning(message: string): void;
|
|
140
|
-
showNewVersionNotification(newVersion: string): void;
|
|
141
140
|
clearEditor(): void;
|
|
142
141
|
updatePendingMessagesDisplay(): void;
|
|
143
142
|
queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
|
|
@@ -7,7 +7,6 @@ import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
|
7
7
|
import { BranchSummaryMessageComponent } from "../../modes/components/branch-summary-message";
|
|
8
8
|
import { CompactionSummaryMessageComponent } from "../../modes/components/compaction-summary-message";
|
|
9
9
|
import { CustomMessageComponent } from "../../modes/components/custom-message";
|
|
10
|
-
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
11
10
|
import {
|
|
12
11
|
createSystemGutter,
|
|
13
12
|
createTextGutter,
|
|
@@ -456,23 +455,6 @@ export class UiHelpers {
|
|
|
456
455
|
this.ctx.ui.requestRender();
|
|
457
456
|
}
|
|
458
457
|
|
|
459
|
-
showNewVersionNotification(newVersion: string): void {
|
|
460
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
461
|
-
this.ctx.chatContainer.addChild(new DynamicBorder(text => theme.fg("warning", text)));
|
|
462
|
-
this.ctx.chatContainer.addChild(
|
|
463
|
-
new Text(
|
|
464
|
-
theme.bold(theme.fg("warning", "Update Available")) +
|
|
465
|
-
"\n" +
|
|
466
|
-
theme.fg("muted", `New version ${newVersion} is available. Run: `) +
|
|
467
|
-
theme.fg("contentAccent", "xcsh update"),
|
|
468
|
-
1,
|
|
469
|
-
0,
|
|
470
|
-
),
|
|
471
|
-
);
|
|
472
|
-
this.ctx.chatContainer.addChild(new DynamicBorder(text => theme.fg("warning", text)));
|
|
473
|
-
this.ctx.ui.requestRender();
|
|
474
|
-
}
|
|
475
|
-
|
|
476
458
|
updatePendingMessagesDisplay(): void {
|
|
477
459
|
this.ctx.pendingMessagesContainer.clear();
|
|
478
460
|
const queuedMessages = this.ctx.session.getQueuedMessages() as QueuedMessages;
|