@f5xc-salesdemos/xcsh 18.46.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.46.0",
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.46.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.46.0",
53
- "@f5xc-salesdemos/pi-ai": "18.46.0",
54
- "@f5xc-salesdemos/pi-natives": "18.46.0",
55
- "@f5xc-salesdemos/pi-tui": "18.46.0",
56
- "@f5xc-salesdemos/pi-utils": "18.46.0",
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": { type: "boolean", default: true },
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": { type: "boolean", default: true },
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": { type: "boolean", default: true },
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.enablePiUser": { type: "boolean", default: true },
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": { type: "boolean", default: true },
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
 
@@ -1956,6 +2006,7 @@ export interface SkillsSettings {
1956
2006
  enableCodexUser?: boolean;
1957
2007
  enableClaudeUser?: boolean;
1958
2008
  enableClaudeProject?: boolean;
2009
+ enableClaudePlugins?: boolean;
1959
2010
  enablePiUser?: boolean;
1960
2011
  enablePiProject?: boolean;
1961
2012
  customDirectories?: string[];
@@ -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
- // For other providers (agents, claude-plugins, etc.), treat them as built-in skill sources.
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.46.0",
21
- "commit": "c70ff40053bde7f2b4ba3a16404328dd63afcc17",
22
- "shortCommit": "c70ff40",
20
+ "version": "18.48.0",
21
+ "commit": "63d4296be7c178ec26412417373def2bfbd644cb",
22
+ "shortCommit": "63d4296",
23
23
  "branch": "main",
24
- "tag": "v18.46.0",
25
- "commitDate": "2026-05-06T05:14:35Z",
26
- "buildDate": "2026-05-06T05:34:34.075Z",
27
- "dirty": false,
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/c70ff40053bde7f2b4ba3a16404328dd63afcc17",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.46.0"
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";
@@ -399,3 +400,126 @@ export function mapGitHubStatus(status: WelcomeGitHubStatus | undefined): Servic
399
400
  return { name: "GitHub", state: "unauthenticated", hint: "run: gh auth login" };
400
401
  }
401
402
  }
403
+
404
+ export type AzureCheckState = "connected" | "auth_error";
405
+
406
+ export interface WelcomeAzureStatus {
407
+ state: AzureCheckState;
408
+ }
409
+
410
+ export async function checkAzureStatus(): Promise<WelcomeAzureStatus | undefined> {
411
+ try {
412
+ if (!$which("az")) return undefined;
413
+ const result = await $`az account show --output json`.quiet().nothrow();
414
+ return { state: result.exitCode === 0 ? "connected" : "auth_error" };
415
+ } catch (err) {
416
+ logger.warn("Azure startup check failed", { error: String(err) });
417
+ return { state: "auth_error" };
418
+ }
419
+ }
420
+
421
+ export function mapAzureStatus(status: WelcomeAzureStatus | undefined): ServiceStatus {
422
+ if (!status) return { name: "Azure", state: "unavailable", hint: "not installed" };
423
+ switch (status.state) {
424
+ case "connected":
425
+ return { name: "Azure", state: "connected" };
426
+ case "auth_error":
427
+ return { name: "Azure", state: "unauthenticated", hint: "run: az login --use-device-code" };
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,10 +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,
56
+ checkAzureStatus,
57
+ checkGcloudStatus,
54
58
  checkGitHubStatus,
55
59
  checkGitLabStatus,
60
+ checkProfileStatus,
56
61
  checkSalesforceStatus,
62
+ mapAwsStatus,
63
+ mapAzureStatus,
57
64
  mapContextStatus,
65
+ mapGcloudStatus,
58
66
  mapGitHubStatus,
59
67
  mapGitLabStatus,
60
68
  mapSalesforceStatus,
@@ -316,16 +324,33 @@ export class InteractiveMode implements InteractiveModeContext {
316
324
  getProjectDir(),
317
325
  );
318
326
 
319
- // Run blocking welcome screen status checks (model + context + gitlab + github + salesforce) in parallel
320
- const [welcomeResult, gitlabStatus, salesforceStatus, githubStatus] = await Promise.all([
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([
321
338
  logger.time("InteractiveMode.init:welcomeChecks", () =>
322
339
  runWelcomeChecks(this.session.model, this.session.modelRegistry.authStorage),
323
340
  ),
324
341
  checkGitLabStatus(getProjectDir()).catch(() => undefined),
325
342
  checkSalesforceStatus(getProjectDir()).catch(() => undefined),
326
343
  checkGitHubStatus().catch(() => undefined),
344
+ checkAzureStatus().catch(() => undefined),
345
+ checkAwsStatus().catch(() => undefined),
346
+ checkGcloudStatus().catch(() => undefined),
347
+ checkProfileStatus().catch(() => undefined),
327
348
  ]);
328
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
+ }
329
354
  const startupQuiet = settings.get("startup.quiet");
330
355
  this.#welcomeComponent = undefined;
331
356
 
@@ -344,6 +369,9 @@ export class InteractiveMode implements InteractiveModeContext {
344
369
  mapGitLabStatus(gitlabStatus),
345
370
  mapGitHubStatus(githubStatus),
346
371
  mapSalesforceStatus(salesforceStatus),
372
+ mapAzureStatus(azureStatus),
373
+ mapAwsStatus(awsStatus),
374
+ mapGcloudStatus(gcloudStatus),
347
375
  ]
348
376
  : [];
349
377
  this.#welcomeComponent = new WelcomeComponent(
@@ -351,6 +379,7 @@ export class InteractiveMode implements InteractiveModeContext {
351
379
  welcomeResult.model,
352
380
  services,
353
381
  this.#initialUpdateStatus,
382
+ profileStatus,
354
383
  );
355
384
 
356
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,
@@ -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);