@f5-sales-demo/xcsh 19.58.0 → 19.58.1
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.58.
|
|
4
|
+
"version": "19.58.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
57
57
|
"@mozilla/readability": "^0.6",
|
|
58
|
-
"@f5-sales-demo/xcsh-stats": "19.58.
|
|
59
|
-
"@f5-sales-demo/pi-agent-core": "19.58.
|
|
60
|
-
"@f5-sales-demo/pi-ai": "19.58.
|
|
61
|
-
"@f5-sales-demo/pi-natives": "19.58.
|
|
62
|
-
"@f5-sales-demo/pi-resource-management": "19.58.
|
|
63
|
-
"@f5-sales-demo/pi-tui": "19.58.
|
|
64
|
-
"@f5-sales-demo/pi-utils": "19.58.
|
|
58
|
+
"@f5-sales-demo/xcsh-stats": "19.58.1",
|
|
59
|
+
"@f5-sales-demo/pi-agent-core": "19.58.1",
|
|
60
|
+
"@f5-sales-demo/pi-ai": "19.58.1",
|
|
61
|
+
"@f5-sales-demo/pi-natives": "19.58.1",
|
|
62
|
+
"@f5-sales-demo/pi-resource-management": "19.58.1",
|
|
63
|
+
"@f5-sales-demo/pi-tui": "19.58.1",
|
|
64
|
+
"@f5-sales-demo/pi-utils": "19.58.1",
|
|
65
65
|
"@sinclair/typebox": "^0.34",
|
|
66
66
|
"@xterm/headless": "^6.0",
|
|
67
67
|
"ajv": "^8.20",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.58.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.58.1",
|
|
21
|
+
"commit": "f72a3fa845f24642e4db4d84aa427bb2e2d64538",
|
|
22
|
+
"shortCommit": "f72a3fa",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.58.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.58.1",
|
|
25
|
+
"commitDate": "2026-07-05T13:33:19Z",
|
|
26
|
+
"buildDate": "2026-07-05T13:53:37.495Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.58.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/f72a3fa845f24642e4db4d84aa427bb2e2d64538",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.58.1"
|
|
33
33
|
};
|
package/src/main.ts
CHANGED
|
@@ -57,6 +57,7 @@ import type { SubmittedUserInput } from "./modes/types";
|
|
|
57
57
|
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage } from "./sdk";
|
|
58
58
|
import type { AgentSession } from "./session/agent-session";
|
|
59
59
|
import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
|
|
60
|
+
import { profileDump, profileMark } from "./startup-profile";
|
|
60
61
|
import { resolvePromptInput } from "./system-prompt";
|
|
61
62
|
import type { LspStartupServerInfo } from "./tools";
|
|
62
63
|
import type { EventBus } from "./utils/event-bus";
|
|
@@ -146,8 +147,6 @@ export async function submitInteractiveInput(
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
const INITIAL_UPDATE_CHECK_TIMEOUT_MS = 500;
|
|
150
|
-
|
|
151
150
|
async function runInteractiveMode(
|
|
152
151
|
session: AgentSession,
|
|
153
152
|
version: string,
|
|
@@ -161,23 +160,26 @@ async function runInteractiveMode(
|
|
|
161
160
|
initialMessage?: string,
|
|
162
161
|
initialImages?: ImageContent[],
|
|
163
162
|
): Promise<void> {
|
|
164
|
-
|
|
165
|
-
versionCheckPromise.catch(() => undefined),
|
|
166
|
-
new Promise<string | undefined>(resolve => setTimeout(() => resolve(undefined), INITIAL_UPDATE_CHECK_TIMEOUT_MS)),
|
|
167
|
-
]);
|
|
163
|
+
profileMark("interactive: enter runInteractiveMode");
|
|
168
164
|
|
|
169
165
|
const mode = new InteractiveMode(session, version, setExtensionUIContext, lspServers, mcpManager, eventBus);
|
|
170
166
|
|
|
171
167
|
await mode.init();
|
|
168
|
+
profileMark("interactive: mode.init() done");
|
|
172
169
|
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// resolves
|
|
176
|
-
if (
|
|
177
|
-
|
|
170
|
+
// Update notice: fully non-blocking. Startup never waits on the network version
|
|
171
|
+
// check; surface the notice whenever it resolves (typically after the prompt is
|
|
172
|
+
// already up). If it never resolves, the update is available on demand via /plugins.
|
|
173
|
+
if (settings.get("startup.checkUpdate")) {
|
|
174
|
+
void versionCheckPromise
|
|
175
|
+
.then(latest => {
|
|
176
|
+
if (latest) mode.showStatus(`Update available: v${latest} — run: xcsh update`, { dim: true });
|
|
177
|
+
})
|
|
178
|
+
.catch(() => {});
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
mode.renderInitialMessages();
|
|
182
|
+
profileMark("interactive: initial render requested");
|
|
181
183
|
|
|
182
184
|
for (const notify of notifs) {
|
|
183
185
|
if (!notify) {
|
|
@@ -210,6 +212,8 @@ async function runInteractiveMode(
|
|
|
210
212
|
}
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
profileMark("interactive: READY — awaiting first input");
|
|
216
|
+
profileDump();
|
|
213
217
|
while (true) {
|
|
214
218
|
const input = await mode.getUserInput();
|
|
215
219
|
await submitInteractiveInput(mode, session, input);
|
|
@@ -551,6 +555,7 @@ async function buildSessionOptions(
|
|
|
551
555
|
|
|
552
556
|
export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<void> {
|
|
553
557
|
logger.startTiming();
|
|
558
|
+
profileMark("entry: runtime + module-graph loaded (pre-main)");
|
|
554
559
|
|
|
555
560
|
// Initialize theme early with defaults (CLI commands need symbols)
|
|
556
561
|
// Will be re-initialized with user preferences later
|
|
@@ -895,6 +900,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
895
900
|
sessionOptions,
|
|
896
901
|
);
|
|
897
902
|
logger.time("main:afterCreateSession");
|
|
903
|
+
profileMark("createAgentSession done");
|
|
898
904
|
if (parsedArgs.apiKey && !sessionOptions.model && session.model) {
|
|
899
905
|
authStorage.setRuntimeApiKey(session.model.provider, parsedArgs.apiKey);
|
|
900
906
|
}
|
|
@@ -274,10 +274,12 @@ export class ExtensionUiController {
|
|
|
274
274
|
this.showExtensionError(error.extensionPath, error.error);
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
-
// Emit session_start
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
// Emit session_start in the BACKGROUND so a slow hook (e.g. a plugin doing a
|
|
278
|
+
// network/CLI check) can never block the TUI paint. The runner discards
|
|
279
|
+
// session_start handler results, so nothing downstream depends on completion;
|
|
280
|
+
// hooks that update the UI (widgets/status) render progressively as they finish.
|
|
281
|
+
// Handler errors are still surfaced via the onError subscription above.
|
|
282
|
+
void extensionRunner.emit({ type: "session_start" });
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void {
|
|
@@ -35,6 +35,7 @@ import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" wit
|
|
|
35
35
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
36
36
|
import { HistoryStorage } from "../session/history-storage";
|
|
37
37
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
38
|
+
import { profileMark } from "../startup-profile";
|
|
38
39
|
import { STTController, type SttState } from "../stt";
|
|
39
40
|
import type { ExitPlanModeDetails } from "../tools";
|
|
40
41
|
import type { EventBus } from "../utils/event-bus";
|
|
@@ -294,6 +295,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
294
295
|
async init(): Promise<void> {
|
|
295
296
|
if (this.isInitialized) return;
|
|
296
297
|
|
|
298
|
+
profileMark("init: start");
|
|
297
299
|
logger.time("InteractiveMode.init:keybindings");
|
|
298
300
|
this.keybindings = KeybindingsManager.create();
|
|
299
301
|
|
|
@@ -305,6 +307,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
305
307
|
this.refreshSlashCommandState.bind(this),
|
|
306
308
|
getProjectDir(),
|
|
307
309
|
);
|
|
310
|
+
profileMark("init: refreshSlashCommandState done");
|
|
308
311
|
|
|
309
312
|
// Refresh user profile in background — fire and forget
|
|
310
313
|
reconcileFromCollectors().catch(err => logger.warn("Background profile refresh failed", { error: String(err) }));
|
|
@@ -368,10 +371,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
368
371
|
|
|
369
372
|
// Load initial todos
|
|
370
373
|
await this.#loadTodoList();
|
|
374
|
+
profileMark("init: loadTodoList done");
|
|
371
375
|
|
|
372
376
|
// Start the UI
|
|
373
377
|
const clearScreen = settings.get("startup.clearScreen");
|
|
374
378
|
this.ui.start(clearScreen);
|
|
379
|
+
profileMark("init: ui.start done");
|
|
375
380
|
pushTerminalTitle();
|
|
376
381
|
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
377
382
|
this.#syncEditorMaxHeight();
|
|
@@ -379,9 +384,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
379
384
|
|
|
380
385
|
// Initialize hooks with TUI-based UI context
|
|
381
386
|
await this.initHooksAndCustomTools();
|
|
387
|
+
profileMark("init: initHooksAndCustomTools done");
|
|
382
388
|
|
|
383
389
|
// Restore mode from session (e.g. plan mode on resume)
|
|
384
390
|
await this.#restoreModeFromSession();
|
|
391
|
+
profileMark("init: restoreModeFromSession done");
|
|
385
392
|
|
|
386
393
|
// Subscribe to agent events
|
|
387
394
|
this.#subscribeToAgent();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in startup profiler — `PI_STARTUP_PROFILE=1`.
|
|
3
|
+
*
|
|
4
|
+
* PI_TIMING exits before the TUI paints, so it cannot measure `runInteractiveMode`
|
|
5
|
+
* or the pre-`main` module-graph load. This records labeled timestamps (ms since
|
|
6
|
+
* process start, via `performance.now()`, which is measured from runtime start) and
|
|
7
|
+
* dumps the full boot timeline to a FILE at the input-ready point — so nothing
|
|
8
|
+
* corrupts the live TUI screen. Zero cost when the env var is unset.
|
|
9
|
+
*
|
|
10
|
+
* The FIRST mark's timestamp is the time from process start to that mark, i.e. it
|
|
11
|
+
* captures the runtime init + evaluation of the embedded module graph that happens
|
|
12
|
+
* before any of our code runs. Later marks show the per-step deltas through paint.
|
|
13
|
+
*
|
|
14
|
+
* Usage: PI_STARTUP_PROFILE=1 xcsh (then quit; read /tmp/xcsh-startup-profile.txt,
|
|
15
|
+
* or set PI_STARTUP_PROFILE_FILE to choose the path).
|
|
16
|
+
*/
|
|
17
|
+
import { writeFileSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
const ENABLED = !!process.env.PI_STARTUP_PROFILE;
|
|
20
|
+
const marks: Array<[label: string, atMs: number]> = [];
|
|
21
|
+
|
|
22
|
+
/** Record a labeled timestamp (no-op unless PI_STARTUP_PROFILE is set). */
|
|
23
|
+
export function profileMark(label: string): void {
|
|
24
|
+
if (ENABLED) marks.push([label, performance.now()]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Write the collected timeline to a file (no-op unless enabled / no marks). */
|
|
28
|
+
export function profileDump(): void {
|
|
29
|
+
if (!ENABLED || marks.length === 0) return;
|
|
30
|
+
const lines = ["=== xcsh startup profile (ms since process start) ===", ""];
|
|
31
|
+
let prev = 0;
|
|
32
|
+
for (const [label, at] of marks) {
|
|
33
|
+
const delta = at - prev;
|
|
34
|
+
lines.push(` ${at.toFixed(1).padStart(9)} ms (+${delta.toFixed(1).padStart(8)} ms) ${label}`);
|
|
35
|
+
prev = at;
|
|
36
|
+
}
|
|
37
|
+
lines.push(
|
|
38
|
+
"",
|
|
39
|
+
` first mark = runtime + module-graph load; total to ready = ${marks[marks.length - 1][1].toFixed(1)} ms`,
|
|
40
|
+
);
|
|
41
|
+
const file = process.env.PI_STARTUP_PROFILE_FILE || "/tmp/xcsh-startup-profile.txt";
|
|
42
|
+
try {
|
|
43
|
+
writeFileSync(file, `${lines.join("\n")}\n`);
|
|
44
|
+
process.stderr.write(`[xcsh] startup profile written to ${file}\n`);
|
|
45
|
+
} catch {
|
|
46
|
+
/* best-effort diagnostic */
|
|
47
|
+
}
|
|
48
|
+
}
|