@f5xc-salesdemos/xcsh 19.28.2 → 19.29.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": "@f5xc-salesdemos/xcsh",
4
- "version": "19.28.2",
4
+ "version": "19.29.1",
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",
@@ -50,12 +50,12 @@
50
50
  "dependencies": {
51
51
  "@agentclientprotocol/sdk": "0.16.1",
52
52
  "@mozilla/readability": "^0.6",
53
- "@f5xc-salesdemos/xcsh-stats": "19.28.2",
54
- "@f5xc-salesdemos/pi-agent-core": "19.28.2",
55
- "@f5xc-salesdemos/pi-ai": "19.28.2",
56
- "@f5xc-salesdemos/pi-natives": "19.28.2",
57
- "@f5xc-salesdemos/pi-tui": "19.28.2",
58
- "@f5xc-salesdemos/pi-utils": "19.28.2",
53
+ "@f5xc-salesdemos/xcsh-stats": "19.29.1",
54
+ "@f5xc-salesdemos/pi-agent-core": "19.29.1",
55
+ "@f5xc-salesdemos/pi-ai": "19.29.1",
56
+ "@f5xc-salesdemos/pi-natives": "19.29.1",
57
+ "@f5xc-salesdemos/pi-tui": "19.29.1",
58
+ "@f5xc-salesdemos/pi-utils": "19.29.1",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
61
  "ajv": "^8.20",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.28.2",
21
- "commit": "ebd6c0383d5c0e5bde390d93e4b6346d311efdae",
22
- "shortCommit": "ebd6c03",
20
+ "version": "19.29.1",
21
+ "commit": "87b69f04b6efe97f6f5d1f849b61ee0f01e448e0",
22
+ "shortCommit": "87b69f0",
23
23
  "branch": "main",
24
- "tag": "v19.28.2",
25
- "commitDate": "2026-06-12T12:35:42Z",
26
- "buildDate": "2026-06-12T13:06:01.155Z",
24
+ "tag": "v19.29.1",
25
+ "commitDate": "2026-06-12T18:13:55Z",
26
+ "buildDate": "2026-06-12T18:33:56.944Z",
27
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/ebd6c0383d5c0e5bde390d93e4b6346d311efdae",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.28.2"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/87b69f04b6efe97f6f5d1f849b61ee0f01e448e0",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.29.1"
33
33
  };
@@ -666,3 +666,12 @@ export function addSection(
666
666
  sections.push({ label: titled, lines });
667
667
  }
668
668
  }
669
+
670
+ export function humanizeResourceType(raw: string): string {
671
+ return raw
672
+ .replace(/^http_/, "")
673
+ .replace(/^tcp_/, "TCP ")
674
+ .replace(/_/g, " ")
675
+ .replace(/([a-z])(balancer|checker)/gi, "$1 $2")
676
+ .replace(/s$/, "");
677
+ }
@@ -8,6 +8,7 @@ import { CachedOutputBlock, F5_TOOL_BORDER_COLOR, renderStatusLine } from "../tu
8
8
  import {
9
9
  formatErrorMessage,
10
10
  formatTimestamp,
11
+ humanizeResourceType,
11
12
  addSection as pushSection,
12
13
  replaceTabs,
13
14
  stripEmpty,
@@ -91,7 +92,15 @@ function splitResultContent(textContent: string, isError: boolean): { json?: str
91
92
 
92
93
  if (!isError) {
93
94
  const pretty = tryPrettyJson(body);
94
- return pretty ? { json: pretty, raw: body } : { raw: body };
95
+ if (pretty) return { json: pretty, raw: body };
96
+ // Body may contain "compactJSON\n\nAI guidance text" — split and discard guidance for display
97
+ const split = body.indexOf("\n\n");
98
+ if (split >= 0) {
99
+ const jsonPart = body.slice(0, split);
100
+ const prettyPart = tryPrettyJson(jsonPart);
101
+ if (prettyPart) return { json: prettyPart, raw: body };
102
+ }
103
+ return { raw: body };
95
104
  }
96
105
 
97
106
  // Error: body is "compactJSON\n\nguidanceText"
@@ -263,15 +272,10 @@ export const xcshApiToolRenderer = {
263
272
  const emptyList = Array.isArray(parsed?.items) && (parsed!.items as unknown[]).length === 0;
264
273
 
265
274
  if ((emptyBody || emptyList) && !guidance) {
266
- // Contextual success message based on HTTP method and response shape
267
- let successMessage = emptyList ? "No items found." : "Empty response";
268
- const rn = resourceName ? ` \u2018${resourceName}\u2019` : "";
269
- if (!emptyList && status >= 200 && status < 300) {
270
- if (method === "DELETE") successMessage = `Resource${rn} deleted successfully.`;
271
- else if (method === "POST") successMessage = `Resource${rn} created successfully.`;
272
- else if (method === "PUT" || method === "PATCH") successMessage = `Resource${rn} updated successfully.`;
275
+ if (emptyList) {
276
+ addSection("Response", [uiTheme.fg("dim", "No items found.")]);
273
277
  }
274
- addSection("Response", [uiTheme.fg("dim", successMessage)]);
278
+ // Empty mutation bodies ({}) are handled by the Result section below \u2014 no Response section needed
275
279
  } else if (json && parsed) {
276
280
  // Branch 1: List response with named items — compact summary
277
281
  const items = parsed.items;
@@ -343,15 +347,38 @@ export const xcshApiToolRenderer = {
343
347
  );
344
348
  }
345
349
 
346
- // Section 4: Error guidance (for HTTP error responses)
347
- if (guidance) {
348
- // Extract the API's specific error message from JSON body for prominent display
349
- const guidanceLines: string[] = [];
350
- const apiMessage =
351
- parsed && typeof parsed.message === "string" ? stripProtobufPrefix(parsed.message) : undefined;
352
- if (apiMessage) guidanceLines.push(uiTheme.fg("error", apiMessage));
353
- guidanceLines.push(uiTheme.fg("warning", guidance));
354
- addSection("Guidance", guidanceLines);
350
+ // Section: Result human-readable glyph status at the bottom of the panel
351
+ {
352
+ const resultLines: string[] = [];
353
+ const infoIcon = uiTheme.styledSymbol("status.success", "chromeAccent");
354
+ const errorIcon = uiTheme.styledSymbol("status.error", "error");
355
+
356
+ if (isError) {
357
+ const apiMessage =
358
+ parsed && typeof parsed.message === "string" ? stripProtobufPrefix(parsed.message) : undefined;
359
+ const errorLabel = apiMessage ?? (status > 0 ? `Request failed (${status})` : "Request failed");
360
+ resultLines.push(`${errorIcon} ${uiTheme.fg("warning", errorLabel)}`);
361
+ if (guidance) resultLines.push(` ${uiTheme.fg("dim", `Try: ${guidance}`)}`);
362
+ } else if (details?.mutationVerb && details?.resourceLabel) {
363
+ resultLines.push(`${infoIcon} ${details.mutationVerb} ${details.resourceLabel}`);
364
+ } else if (method === "GET" && parsed) {
365
+ const items = parsed.items;
366
+ if (Array.isArray(items)) {
367
+ const rawType = pathParts.at(-1) ?? "items";
368
+ const typeLabel = humanizeResourceType(rawType);
369
+ const plural = items.length === 1 ? typeLabel : `${typeLabel}s`;
370
+ resultLines.push(`${infoIcon} ${items.length} ${plural}`);
371
+ } else {
372
+ const rawType = pathParts.at(-2) ?? "";
373
+ const name = resourceName ?? "";
374
+ const label = rawType && name ? `${humanizeResourceType(rawType)} '${name}'` : displayPath;
375
+ resultLines.push(`${infoIcon} Loaded ${label}`);
376
+ }
377
+ } else if (status >= 200 && status < 300) {
378
+ resultLines.push(`${infoIcon} OK`);
379
+ }
380
+
381
+ if (resultLines.length > 0) addSection("Result", resultLines);
355
382
  }
356
383
 
357
384
  // --- Render with CachedOutputBlock ---
@@ -5,6 +5,7 @@ import { type Static, Type } from "@sinclair/typebox";
5
5
  import xcshApiDescription from "../prompts/tools/xcsh-api.md" with { type: "text" };
6
6
  import { type ContextEnv, createContextEnv } from "../services/context-env";
7
7
  import type { ToolSession } from ".";
8
+ import { humanizeResourceType } from "./render-utils";
8
9
 
9
10
  // Namespace filtering driven by x-f5xc-namespace-profile from enriched API specs.
10
11
 
@@ -108,6 +109,10 @@ export interface XcshApiToolDetails {
108
109
  batchTotalItems?: number;
109
110
  /** The resolved JSON body string sent to the API (after $F5XC_* expansion). */
110
111
  resolvedPayload?: string;
112
+ /** Human-readable verb for mutation results. */
113
+ mutationVerb?: "Created" | "Updated" | "Deleted";
114
+ /** Human-readable resource label, e.g. "load balancer 'rm-headers'". */
115
+ resourceLabel?: string;
111
116
  }
112
117
 
113
118
  type XcshApiResult = AgentToolResult<XcshApiToolDetails> & { isError?: boolean };
@@ -638,6 +643,10 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
638
643
  }
639
644
  case 409:
640
645
  return `Resource already exists${ctxHint}. Use PUT to replace the existing resource, or DELETE it first before creating a new one.`;
646
+ case 400:
647
+ return `Bad request${ctxHint}. Check the payload for missing required fields or invalid values.`;
648
+ case 422:
649
+ return `Validation failed${ctxHint}. The request payload has field-level errors — check the message above.`;
641
650
  case 429:
642
651
  return `API rate limit exceeded${ctxHint}. Wait briefly and retry the request.`;
643
652
  default:
@@ -817,13 +826,6 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
817
826
  // Label changes help the model echo type names in its response (Finding 13).
818
827
  // INTERACTION EFFECT: this change + TCP protocol label are synergistic (Finding 31).
819
828
  const pathSegs = resolvedPath.split("/").filter(Boolean);
820
- const humanizeResourceType = (raw: string): string =>
821
- raw
822
- .replace(/^http_/, "")
823
- .replace(/^tcp_/, "TCP ")
824
- .replace(/_/g, " ")
825
- .replace(/([a-z])(balancer|checker)/gi, "$1 $2")
826
- .replace(/s$/, "");
827
829
  let resourceLabel: string;
828
830
  if (params.method === "POST") {
829
831
  const rawType = pathSegs.at(-1) ?? "";
@@ -835,6 +837,9 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
835
837
  const rawType = pathSegs.at(-2) ?? "";
836
838
  resourceLabel = name && rawType ? `${humanizeResourceType(rawType)} ${name}` : resolvedPath;
837
839
  }
840
+ detail.mutationVerb = verb as "Created" | "Updated" | "Deleted";
841
+ detail.resourceLabel = resourceLabel;
842
+
838
843
  // POST returns the full resource; PUT/DELETE return {}.
839
844
  // Only claim response contains the resource for POST to avoid misleading the model.
840
845
  const resourceHint =