@f5xc-salesdemos/xcsh 18.47.0 → 18.48.0
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/autoresearch/tools/init-experiment.ts +2 -2
- package/src/autoresearch/tools/log-experiment.ts +2 -2
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/config/settings-schema.ts +55 -5
- package/src/extensibility/skills.ts +2 -1
- package/src/internal-urls/build-info.generated.ts +9 -9
- package/src/modes/components/welcome-checks.ts +97 -0
- package/src/modes/components/welcome.ts +33 -1
- package/src/modes/interactive-mode.ts +27 -2
- package/src/prompts/system/system-prompt.md +13 -0
- package/src/sdk.ts +24 -0
- package/src/system-prompt.ts +7 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.48.0",
|
|
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",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
50
50
|
"@mozilla/readability": "^0.6",
|
|
51
|
-
"@f5xc-salesdemos/xcsh-stats": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
51
|
+
"@f5xc-salesdemos/xcsh-stats": "18.48.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "18.48.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "18.48.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "18.48.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "18.48.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "18.48.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34",
|
|
58
58
|
"@xterm/headless": "^6.0",
|
|
59
59
|
"ajv": "^8.18",
|
|
@@ -385,8 +385,8 @@ export function createInitExperimentTool(
|
|
|
385
385
|
};
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
function renderInitCall(name: string, theme: Theme, width?: number): string {
|
|
389
|
-
return `${theme.fg("toolTitle", theme.bold("init_experiment"))} ${theme.fg("contentAccent", truncateToWidth(replaceTabs(name), Math.max(20, (width ?? 100) - 20)))}`;
|
|
388
|
+
function renderInitCall(name: string | undefined, theme: Theme, width?: number): string {
|
|
389
|
+
return `${theme.fg("toolTitle", theme.bold("init_experiment"))} ${theme.fg("contentAccent", truncateToWidth(replaceTabs(name ?? ""), Math.max(20, (width ?? 100) - 20)))}`;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
function collectLoggedRunNumbers(results: ExperimentState["results"]): Set<number> {
|
|
@@ -362,7 +362,7 @@ export function createLogExperimentTool(
|
|
|
362
362
|
const color = args.status === "keep" ? "success" : args.status === "discard" ? "warning" : "error";
|
|
363
363
|
return {
|
|
364
364
|
render(width: number): string[] {
|
|
365
|
-
const description = truncateToWidth(replaceTabs(args.description), Math.max(20, width - 30));
|
|
365
|
+
const description = truncateToWidth(replaceTabs(args.description ?? ""), Math.max(20, width - 30));
|
|
366
366
|
return [
|
|
367
367
|
`${theme.fg("toolTitle", theme.bold("log_experiment"))} ${theme.fg(color, args.status)} ${theme.fg("muted", description)}`,
|
|
368
368
|
];
|
|
@@ -773,7 +773,7 @@ function truncateAsiValue(value: ASIData[string]): string {
|
|
|
773
773
|
function renderSummary(details: LogDetails, theme: Theme, width?: number): string {
|
|
774
774
|
const { experiment, state } = details;
|
|
775
775
|
const color = experiment.status === "keep" ? "success" : experiment.status === "discard" ? "warning" : "error";
|
|
776
|
-
let summary = `${theme.fg(color, experiment.status.toUpperCase())} ${theme.fg("muted", truncateToWidth(replaceTabs(experiment.description), Math.max(20, (width ?? 100) - 30)))}`;
|
|
776
|
+
let summary = `${theme.fg(color, experiment.status.toUpperCase())} ${theme.fg("muted", truncateToWidth(replaceTabs(experiment.description ?? ""), Math.max(20, (width ?? 100) - 30)))}`;
|
|
777
777
|
summary += ` ${theme.fg("contentAccent", `${state.metricName}=${formatNum(experiment.metric, state.metricUnit)}`)}`;
|
|
778
778
|
if (state.bestMetric !== null) {
|
|
779
779
|
summary += ` ${theme.fg("dim", `baseline ${formatNum(state.bestMetric, state.metricUnit)}`)}`;
|
|
@@ -383,7 +383,7 @@ export function createRunExperimentTool(
|
|
|
383
383
|
renderCall(args, _options, theme): Component {
|
|
384
384
|
return {
|
|
385
385
|
render(width: number): string[] {
|
|
386
|
-
const commandPreview = truncateToWidth(replaceTabs(args.command), Math.max(20, width - 20));
|
|
386
|
+
const commandPreview = truncateToWidth(replaceTabs(args.command ?? ""), Math.max(20, width - 20));
|
|
387
387
|
return [`${theme.fg("toolTitle", theme.bold("run_experiment"))} ${theme.fg("muted", commandPreview)}`];
|
|
388
388
|
},
|
|
389
389
|
invalidate() {},
|
|
@@ -1622,15 +1622,65 @@ export const SETTINGS_SCHEMA = {
|
|
|
1622
1622
|
ui: { tab: "tasks", label: "Skill Commands", description: "Register skills as /skill:name commands" },
|
|
1623
1623
|
},
|
|
1624
1624
|
|
|
1625
|
-
"skills.enableCodexUser": {
|
|
1625
|
+
"skills.enableCodexUser": {
|
|
1626
|
+
type: "boolean",
|
|
1627
|
+
default: false,
|
|
1628
|
+
ui: {
|
|
1629
|
+
tab: "tasks",
|
|
1630
|
+
label: "Codex User Skills",
|
|
1631
|
+
description: "Load skills from Codex configuration",
|
|
1632
|
+
},
|
|
1633
|
+
},
|
|
1626
1634
|
|
|
1627
|
-
"skills.enableClaudeUser": {
|
|
1635
|
+
"skills.enableClaudeUser": {
|
|
1636
|
+
type: "boolean",
|
|
1637
|
+
default: false,
|
|
1638
|
+
ui: {
|
|
1639
|
+
tab: "tasks",
|
|
1640
|
+
label: "Claude User Skills",
|
|
1641
|
+
description: "Load skills from ~/.claude/skills/",
|
|
1642
|
+
},
|
|
1643
|
+
},
|
|
1628
1644
|
|
|
1629
|
-
"skills.enableClaudeProject": {
|
|
1645
|
+
"skills.enableClaudeProject": {
|
|
1646
|
+
type: "boolean",
|
|
1647
|
+
default: false,
|
|
1648
|
+
ui: {
|
|
1649
|
+
tab: "tasks",
|
|
1650
|
+
label: "Claude Project Skills",
|
|
1651
|
+
description: "Load skills from .claude/skills/",
|
|
1652
|
+
},
|
|
1653
|
+
},
|
|
1630
1654
|
|
|
1631
|
-
"skills.
|
|
1655
|
+
"skills.enableClaudePlugins": {
|
|
1656
|
+
type: "boolean",
|
|
1657
|
+
default: false,
|
|
1658
|
+
ui: {
|
|
1659
|
+
tab: "tasks",
|
|
1660
|
+
label: "Claude Marketplace Skills",
|
|
1661
|
+
description: "Load skills from Claude Code marketplace plugins (~/.claude/plugins/cache/)",
|
|
1662
|
+
},
|
|
1663
|
+
},
|
|
1664
|
+
|
|
1665
|
+
"skills.enablePiUser": {
|
|
1666
|
+
type: "boolean",
|
|
1667
|
+
default: true,
|
|
1668
|
+
ui: {
|
|
1669
|
+
tab: "tasks",
|
|
1670
|
+
label: "xcsh User Skills",
|
|
1671
|
+
description: "Load user-level skills from ~/.xcsh/agent/skills/",
|
|
1672
|
+
},
|
|
1673
|
+
},
|
|
1632
1674
|
|
|
1633
|
-
"skills.enablePiProject": {
|
|
1675
|
+
"skills.enablePiProject": {
|
|
1676
|
+
type: "boolean",
|
|
1677
|
+
default: true,
|
|
1678
|
+
ui: {
|
|
1679
|
+
tab: "tasks",
|
|
1680
|
+
label: "xcsh Project Skills",
|
|
1681
|
+
description: "Load project-level skills from .xcsh/skills/",
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1634
1684
|
|
|
1635
1685
|
"skills.customDirectories": { type: "array", default: [] as string[] },
|
|
1636
1686
|
|
|
@@ -82,6 +82,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
82
82
|
enableCodexUser = true,
|
|
83
83
|
enableClaudeUser = true,
|
|
84
84
|
enableClaudeProject = true,
|
|
85
|
+
enableClaudePlugins = false,
|
|
85
86
|
enablePiUser = true,
|
|
86
87
|
enablePiProject = true,
|
|
87
88
|
customDirectories = [],
|
|
@@ -105,7 +106,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
105
106
|
if (provider === "claude" && level === "project") return enableClaudeProject;
|
|
106
107
|
if (provider === "native" && level === "user") return enablePiUser;
|
|
107
108
|
if (provider === "native" && level === "project") return enablePiProject;
|
|
108
|
-
|
|
109
|
+
if (provider === "claude-plugins") return enableClaudePlugins;
|
|
109
110
|
return anyBuiltInSkillSourceEnabled;
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.48.0",
|
|
21
|
+
"commit": "63d4296be7c178ec26412417373def2bfbd644cb",
|
|
22
|
+
"shortCommit": "63d4296",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
27
|
-
"dirty":
|
|
24
|
+
"tag": "v18.48.0",
|
|
25
|
+
"commitDate": "2026-05-06T13:11:55Z",
|
|
26
|
+
"buildDate": "2026-05-06T14:22:27.362Z",
|
|
27
|
+
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/63d4296be7c178ec26412417373def2bfbd644cb",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.48.0"
|
|
33
33
|
};
|
|
@@ -2,6 +2,7 @@ import type { Model } from "@f5xc-salesdemos/pi-ai";
|
|
|
2
2
|
import { validateApiKeyAgainstModelsEndpoint } from "@f5xc-salesdemos/pi-ai/utils/oauth/api-key-validation";
|
|
3
3
|
import { $which, logger } from "@f5xc-salesdemos/pi-utils";
|
|
4
4
|
import { $ } from "bun";
|
|
5
|
+
import { loadProfile } from "../../internal-urls/user-profile";
|
|
5
6
|
import { type AuthStatus, ContextService } from "../../services/f5xc-context";
|
|
6
7
|
import { deriveTenantFromUrl } from "../../services/f5xc-env";
|
|
7
8
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
@@ -426,3 +427,99 @@ export function mapAzureStatus(status: WelcomeAzureStatus | undefined): ServiceS
|
|
|
426
427
|
return { name: "Azure", state: "unauthenticated", hint: "run: az login --use-device-code" };
|
|
427
428
|
}
|
|
428
429
|
}
|
|
430
|
+
|
|
431
|
+
export type AwsCheckState = "connected" | "auth_error";
|
|
432
|
+
|
|
433
|
+
export interface WelcomeAwsStatus {
|
|
434
|
+
state: AwsCheckState;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function checkAwsStatus(): Promise<WelcomeAwsStatus | undefined> {
|
|
438
|
+
try {
|
|
439
|
+
if (!$which("aws")) return undefined;
|
|
440
|
+
const result = await $`aws sts get-caller-identity --output json`.quiet().nothrow();
|
|
441
|
+
return { state: result.exitCode === 0 ? "connected" : "auth_error" };
|
|
442
|
+
} catch (err) {
|
|
443
|
+
logger.warn("AWS startup check failed", { error: String(err) });
|
|
444
|
+
return { state: "auth_error" };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function mapAwsStatus(status: WelcomeAwsStatus | undefined): ServiceStatus {
|
|
449
|
+
if (!status) return { name: "AWS", state: "unavailable", hint: "not installed" };
|
|
450
|
+
switch (status.state) {
|
|
451
|
+
case "connected":
|
|
452
|
+
return { name: "AWS", state: "connected" };
|
|
453
|
+
case "auth_error":
|
|
454
|
+
return { name: "AWS", state: "unauthenticated", hint: "run: aws configure" };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export type GcloudCheckState = "connected" | "auth_error";
|
|
459
|
+
|
|
460
|
+
export interface WelcomeGcloudStatus {
|
|
461
|
+
state: GcloudCheckState;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function checkGcloudStatus(): Promise<WelcomeGcloudStatus | undefined> {
|
|
465
|
+
try {
|
|
466
|
+
if (!$which("gcloud")) return undefined;
|
|
467
|
+
const result = await $`gcloud auth list --format=value(account)`.quiet().nothrow();
|
|
468
|
+
const hasAccount = result.text().trim().length > 0;
|
|
469
|
+
return { state: hasAccount ? "connected" : "auth_error" };
|
|
470
|
+
} catch (err) {
|
|
471
|
+
logger.warn("Google Cloud startup check failed", { error: String(err) });
|
|
472
|
+
return { state: "auth_error" };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function mapGcloudStatus(status: WelcomeGcloudStatus | undefined): ServiceStatus {
|
|
477
|
+
if (!status) return { name: "Google Cloud", state: "unavailable", hint: "not installed" };
|
|
478
|
+
switch (status.state) {
|
|
479
|
+
case "connected":
|
|
480
|
+
return { name: "Google Cloud", state: "connected" };
|
|
481
|
+
case "auth_error":
|
|
482
|
+
return { name: "Google Cloud", state: "unauthenticated", hint: "run: gcloud auth login" };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export type ProfileCheckState = "current" | "stale" | "missing";
|
|
487
|
+
|
|
488
|
+
export interface WelcomeProfileStatus {
|
|
489
|
+
state: ProfileCheckState;
|
|
490
|
+
name?: string;
|
|
491
|
+
updatedAt?: string;
|
|
492
|
+
staleDays?: number;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const PROFILE_STALE_HOURS = 24;
|
|
496
|
+
|
|
497
|
+
/** Check user profile freshness. Returns undefined only on unexpected error. */
|
|
498
|
+
export async function checkProfileStatus(): Promise<WelcomeProfileStatus | undefined> {
|
|
499
|
+
try {
|
|
500
|
+
const profile = await loadProfile();
|
|
501
|
+
if (!profile.givenName && !profile.familyName) {
|
|
502
|
+
return { state: "missing" };
|
|
503
|
+
}
|
|
504
|
+
const name = [profile.givenName, profile.familyName].filter(Boolean).join(" ");
|
|
505
|
+
const updatedAt = profile.updatedAt;
|
|
506
|
+
|
|
507
|
+
if (!updatedAt) {
|
|
508
|
+
return { state: "stale", name };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const ageHours = (Date.now() - new Date(updatedAt).getTime()) / (1000 * 60 * 60);
|
|
512
|
+
if (ageHours > PROFILE_STALE_HOURS) {
|
|
513
|
+
return {
|
|
514
|
+
state: "stale",
|
|
515
|
+
name,
|
|
516
|
+
updatedAt,
|
|
517
|
+
staleDays: Math.floor(ageHours / 24),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return { state: "current", name, updatedAt };
|
|
522
|
+
} catch {
|
|
523
|
+
return { state: "missing" };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -2,7 +2,7 @@ import { type Component, padding, truncateToWidth, visibleWidth } from "@f5xc-sa
|
|
|
2
2
|
import { APP_NAME } from "@f5xc-salesdemos/pi-utils";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
import { formatStatusIcon } from "../../services/f5xc-context-indicators";
|
|
5
|
-
import type { ModelStatus, ServiceStatus } from "./welcome-checks";
|
|
5
|
+
import type { ModelStatus, ServiceStatus, WelcomeProfileStatus } from "./welcome-checks";
|
|
6
6
|
|
|
7
7
|
export interface UpdateStatus {
|
|
8
8
|
available: boolean;
|
|
@@ -15,6 +15,7 @@ export class WelcomeComponent implements Component {
|
|
|
15
15
|
private modelStatus: ModelStatus,
|
|
16
16
|
private services: ServiceStatus[] = [],
|
|
17
17
|
private updateStatus?: UpdateStatus,
|
|
18
|
+
private profileStatus?: WelcomeProfileStatus,
|
|
18
19
|
) {}
|
|
19
20
|
invalidate(): void {}
|
|
20
21
|
setModelStatus(status: ModelStatus): void {
|
|
@@ -26,6 +27,9 @@ export class WelcomeComponent implements Component {
|
|
|
26
27
|
setUpdateStatus(status: UpdateStatus | undefined): void {
|
|
27
28
|
this.updateStatus = status;
|
|
28
29
|
}
|
|
30
|
+
setProfileStatus(status: WelcomeProfileStatus | undefined): void {
|
|
31
|
+
this.profileStatus = status;
|
|
32
|
+
}
|
|
29
33
|
|
|
30
34
|
render(termWidth: number): string[] {
|
|
31
35
|
const minLeftCol = 48;
|
|
@@ -125,6 +129,9 @@ export class WelcomeComponent implements Component {
|
|
|
125
129
|
for (const svc of this.services) {
|
|
126
130
|
lines.push(this.#renderServiceLine(svc));
|
|
127
131
|
}
|
|
132
|
+
if (this.profileStatus) {
|
|
133
|
+
lines.push(" User Profile", ...this.#renderProfileStatus());
|
|
134
|
+
}
|
|
128
135
|
if (this.#showUpdateSection()) {
|
|
129
136
|
lines.push(this.#renderUpdateLine());
|
|
130
137
|
}
|
|
@@ -148,6 +155,12 @@ export class WelcomeComponent implements Component {
|
|
|
148
155
|
lines.push(this.#renderUpdateLine());
|
|
149
156
|
}
|
|
150
157
|
}
|
|
158
|
+
if (this.profileStatus) {
|
|
159
|
+
lines.push(separator);
|
|
160
|
+
for (const line of this.#renderProfileStatus()) {
|
|
161
|
+
lines.push(line);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
151
164
|
lines.push("");
|
|
152
165
|
return lines;
|
|
153
166
|
}
|
|
@@ -189,6 +202,25 @@ export class WelcomeComponent implements Component {
|
|
|
189
202
|
}
|
|
190
203
|
}
|
|
191
204
|
|
|
205
|
+
#renderProfileStatus(): string[] {
|
|
206
|
+
if (!this.profileStatus) return [];
|
|
207
|
+
const { state, name, staleDays } = this.profileStatus;
|
|
208
|
+
switch (state) {
|
|
209
|
+
case "current":
|
|
210
|
+
return [` ${formatStatusIcon("connected")} ${theme.fg("muted", name ?? "profile loaded")}`];
|
|
211
|
+
case "stale":
|
|
212
|
+
return [
|
|
213
|
+
` ${formatStatusIcon("warning")} ${theme.fg("muted", name ?? "profile")} ${theme.fg("warning", `\u2014 stale${staleDays !== undefined ? ` (${staleDays}d)` : ""}`)}`,
|
|
214
|
+
` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "xcsh://user?seed=true")}`,
|
|
215
|
+
];
|
|
216
|
+
case "missing":
|
|
217
|
+
return [
|
|
218
|
+
` ${formatStatusIcon("warning")} ${theme.fg("warning", "No profile yet")}`,
|
|
219
|
+
` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "xcsh://user?seed=true")}`,
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
192
224
|
#f5ColorLine(line: string): string {
|
|
193
225
|
const red = "\x1b[38;5;160m";
|
|
194
226
|
const white = "\x1b[1;37m";
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
29
29
|
import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slash-commands";
|
|
30
30
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
31
|
+
import { seedProfile } from "../internal-urls/user-profile";
|
|
31
32
|
import { renameApprovedPlanFile } from "../plan-mode/approved-plan";
|
|
32
33
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
33
34
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
@@ -51,12 +52,17 @@ import { StatusLineComponent } from "./components/status-line";
|
|
|
51
52
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
52
53
|
import { type UpdateStatus, WelcomeComponent } from "./components/welcome";
|
|
53
54
|
import {
|
|
55
|
+
checkAwsStatus,
|
|
54
56
|
checkAzureStatus,
|
|
57
|
+
checkGcloudStatus,
|
|
55
58
|
checkGitHubStatus,
|
|
56
59
|
checkGitLabStatus,
|
|
60
|
+
checkProfileStatus,
|
|
57
61
|
checkSalesforceStatus,
|
|
62
|
+
mapAwsStatus,
|
|
58
63
|
mapAzureStatus,
|
|
59
64
|
mapContextStatus,
|
|
65
|
+
mapGcloudStatus,
|
|
60
66
|
mapGitHubStatus,
|
|
61
67
|
mapGitLabStatus,
|
|
62
68
|
mapSalesforceStatus,
|
|
@@ -318,8 +324,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
318
324
|
getProjectDir(),
|
|
319
325
|
);
|
|
320
326
|
|
|
321
|
-
// Run blocking welcome screen status checks
|
|
322
|
-
const [
|
|
327
|
+
// Run blocking welcome screen status checks in parallel
|
|
328
|
+
const [
|
|
329
|
+
welcomeResult,
|
|
330
|
+
gitlabStatus,
|
|
331
|
+
salesforceStatus,
|
|
332
|
+
githubStatus,
|
|
333
|
+
azureStatus,
|
|
334
|
+
awsStatus,
|
|
335
|
+
gcloudStatus,
|
|
336
|
+
profileStatus,
|
|
337
|
+
] = await Promise.all([
|
|
323
338
|
logger.time("InteractiveMode.init:welcomeChecks", () =>
|
|
324
339
|
runWelcomeChecks(this.session.model, this.session.modelRegistry.authStorage),
|
|
325
340
|
),
|
|
@@ -327,8 +342,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
327
342
|
checkSalesforceStatus(getProjectDir()).catch(() => undefined),
|
|
328
343
|
checkGitHubStatus().catch(() => undefined),
|
|
329
344
|
checkAzureStatus().catch(() => undefined),
|
|
345
|
+
checkAwsStatus().catch(() => undefined),
|
|
346
|
+
checkGcloudStatus().catch(() => undefined),
|
|
347
|
+
checkProfileStatus().catch(() => undefined),
|
|
330
348
|
]);
|
|
331
349
|
|
|
350
|
+
// Refresh stale or missing profile in background — fire and forget
|
|
351
|
+
if (profileStatus?.state === "stale" || profileStatus?.state === "missing") {
|
|
352
|
+
seedProfile().catch(err => logger.warn("Background profile refresh failed", { error: String(err) }));
|
|
353
|
+
}
|
|
332
354
|
const startupQuiet = settings.get("startup.quiet");
|
|
333
355
|
this.#welcomeComponent = undefined;
|
|
334
356
|
|
|
@@ -348,6 +370,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
348
370
|
mapGitHubStatus(githubStatus),
|
|
349
371
|
mapSalesforceStatus(salesforceStatus),
|
|
350
372
|
mapAzureStatus(azureStatus),
|
|
373
|
+
mapAwsStatus(awsStatus),
|
|
374
|
+
mapGcloudStatus(gcloudStatus),
|
|
351
375
|
]
|
|
352
376
|
: [];
|
|
353
377
|
this.#welcomeComponent = new WelcomeComponent(
|
|
@@ -355,6 +379,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
355
379
|
welcomeResult.model,
|
|
356
380
|
services,
|
|
357
381
|
this.#initialUpdateStatus,
|
|
382
|
+
profileStatus,
|
|
358
383
|
);
|
|
359
384
|
|
|
360
385
|
// Setup UI layout
|
|
@@ -149,6 +149,17 @@ Use these values when constructing API payloads and resource names.
|
|
|
149
149
|
Available F5 XC documentation topics: {{knowledgeTopics}}.
|
|
150
150
|
{{/if}}
|
|
151
151
|
{{/if}}
|
|
152
|
+
{{#if userProfile}}
|
|
153
|
+
## Primary Human
|
|
154
|
+
|
|
155
|
+
{{userProfile.name}} ({{userProfile.role}}, {{userProfile.org}}).
|
|
156
|
+
Full profile at `xcsh://user`. **MUST** read when:
|
|
157
|
+
- Addressing the user by name or drafting communications from/to them
|
|
158
|
+
- A tool call needs personal identifiers (Salesforce user ID, GitHub username, email, phone)
|
|
159
|
+
- User asks about themselves ("my email", "who is my manager", "where am I from")
|
|
160
|
+
- Answering relationship/identity questions ("who is your human", "who do you work with")
|
|
161
|
+
**SHOULD NOT** read for routine technical work, code changes, or product questions.
|
|
162
|
+
{{/if}}
|
|
152
163
|
|
|
153
164
|
{{#if contextFiles.length}}
|
|
154
165
|
<context>
|
|
@@ -194,6 +205,8 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
|
|
|
194
205
|
- `xcsh://about` — Identity, version, build fingerprint, architecture, self-improvement. **MUST** read for any question about xcsh before exploring `~/.xcsh/`.
|
|
195
206
|
This document contains the authoritative repository URL, issues URL, and source location.
|
|
196
207
|
For identity questions (source code, repo, version, who built this) — answer from `xcsh://about` alone. Do not call external GitHub tools.
|
|
208
|
+
- `xcsh://user` — Primary human user profile (identity, employment, contact, demographics). Read when personal identity context is needed. Do not read proactively on every turn.
|
|
209
|
+
- `xcsh://user?seed=true` — Refresh profile from Salesforce, GitHub, and system sources.
|
|
197
210
|
- `xcsh://api-spec/` — F5 XC API specifications (schema introspection, field types, validation).
|
|
198
211
|
- `xcsh://api-catalog/` — F5 XC API operations catalog (CRUD execution).
|
|
199
212
|
|
package/src/sdk.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
RuleProtocolHandler,
|
|
73
73
|
SkillProtocolHandler,
|
|
74
74
|
} from "./internal-urls";
|
|
75
|
+
import { loadProfile } from "./internal-urls/user-profile";
|
|
75
76
|
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./ipy/executor";
|
|
76
77
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
77
78
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp";
|
|
@@ -1450,6 +1451,27 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1450
1451
|
}
|
|
1451
1452
|
appendPrompt = parts.join("\n\n");
|
|
1452
1453
|
}
|
|
1454
|
+
// Load compact user profile for system prompt hint
|
|
1455
|
+
let userProfile: { name: string; role: string; org: string } | undefined;
|
|
1456
|
+
try {
|
|
1457
|
+
const _profile = await loadProfile();
|
|
1458
|
+
if (_profile.givenName || _profile.familyName) {
|
|
1459
|
+
const _name = [_profile.givenName, _profile.familyName].filter(Boolean).join(" ");
|
|
1460
|
+
if (_name) {
|
|
1461
|
+
userProfile = {
|
|
1462
|
+
name: _name,
|
|
1463
|
+
role: _profile.jobTitle ?? "",
|
|
1464
|
+
org:
|
|
1465
|
+
typeof _profile.worksFor === "string"
|
|
1466
|
+
? _profile.worksFor
|
|
1467
|
+
: ((_profile.worksFor as { name?: string } | undefined)?.name ?? ""),
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
} catch {
|
|
1472
|
+
// No profile — hint block omitted
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1453
1475
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
1454
1476
|
cwd,
|
|
1455
1477
|
skills,
|
|
@@ -1467,6 +1489,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1467
1489
|
eagerTasks,
|
|
1468
1490
|
secretsEnabled,
|
|
1469
1491
|
context: contextForPrompt,
|
|
1492
|
+
userProfile,
|
|
1470
1493
|
knowledgeTopics,
|
|
1471
1494
|
contextSkillDirs,
|
|
1472
1495
|
contextIncludeSkills,
|
|
@@ -1495,6 +1518,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1495
1518
|
eagerTasks,
|
|
1496
1519
|
secretsEnabled,
|
|
1497
1520
|
context: contextForPrompt,
|
|
1521
|
+
userProfile,
|
|
1498
1522
|
knowledgeTopics,
|
|
1499
1523
|
contextSkillDirs,
|
|
1500
1524
|
contextIncludeSkills,
|
package/src/system-prompt.ts
CHANGED
|
@@ -459,6 +459,12 @@ export interface BuildSystemPromptOptions {
|
|
|
459
459
|
credentialSource: string;
|
|
460
460
|
authStatus: string;
|
|
461
461
|
};
|
|
462
|
+
/** Compact user profile hint injected into Workspace section. Omit when no profile exists. */
|
|
463
|
+
userProfile?: {
|
|
464
|
+
name: string;
|
|
465
|
+
role: string;
|
|
466
|
+
org: string;
|
|
467
|
+
};
|
|
462
468
|
knowledgeTopics?: string;
|
|
463
469
|
contextSkillDirs?: string[];
|
|
464
470
|
contextIncludeSkills?: string[];
|
|
@@ -650,6 +656,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
650
656
|
eagerTasks,
|
|
651
657
|
secretsEnabled,
|
|
652
658
|
context,
|
|
659
|
+
userProfile: options.userProfile,
|
|
653
660
|
knowledgeTopics: options.knowledgeTopics,
|
|
654
661
|
};
|
|
655
662
|
let rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|