@f5xc-salesdemos/xcsh 19.28.1 → 19.29.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": "19.28.1",
4
+ "version": "19.29.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",
@@ -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.1",
54
- "@f5xc-salesdemos/pi-agent-core": "19.28.1",
55
- "@f5xc-salesdemos/pi-ai": "19.28.1",
56
- "@f5xc-salesdemos/pi-natives": "19.28.1",
57
- "@f5xc-salesdemos/pi-tui": "19.28.1",
58
- "@f5xc-salesdemos/pi-utils": "19.28.1",
53
+ "@f5xc-salesdemos/xcsh-stats": "19.29.0",
54
+ "@f5xc-salesdemos/pi-agent-core": "19.29.0",
55
+ "@f5xc-salesdemos/pi-ai": "19.29.0",
56
+ "@f5xc-salesdemos/pi-natives": "19.29.0",
57
+ "@f5xc-salesdemos/pi-tui": "19.29.0",
58
+ "@f5xc-salesdemos/pi-utils": "19.29.0",
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.1",
21
- "commit": "e614268dc72eee896142a03c2a926a73c7774e4e",
22
- "shortCommit": "e614268",
20
+ "version": "19.29.0",
21
+ "commit": "7fa5eedd8b420a253446f200bd667bb2e09f7320",
22
+ "shortCommit": "7fa5eed",
23
23
  "branch": "main",
24
- "tag": "v19.28.1",
25
- "commitDate": "2026-06-11T20:00:30Z",
26
- "buildDate": "2026-06-11T20:25:20.808Z",
24
+ "tag": "v19.29.0",
25
+ "commitDate": "2026-06-12T16:42:15Z",
26
+ "buildDate": "2026-06-12T17:15:16.199Z",
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/e614268dc72eee896142a03c2a926a73c7774e4e",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.28.1"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/7fa5eedd8b420a253446f200bd667bb2e09f7320",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.29.0"
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,40 @@ 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 successIcon = uiTheme.styledSymbol("status.success", "success");
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("error", errorLabel)}`);
361
+ if (guidance) resultLines.push(` ${uiTheme.fg("warning", `Try: ${guidance}`)}`);
362
+ } else if (details?.mutationVerb && details?.resourceLabel) {
363
+ resultLines.push(
364
+ `${successIcon} ${uiTheme.fg("success", `${details.mutationVerb} ${details.resourceLabel}`)}`,
365
+ );
366
+ } else if (method === "GET" && parsed) {
367
+ const items = parsed.items;
368
+ if (Array.isArray(items)) {
369
+ const rawType = pathParts.at(-1) ?? "items";
370
+ const typeLabel = humanizeResourceType(rawType);
371
+ const plural = items.length === 1 ? typeLabel : `${typeLabel}s`;
372
+ resultLines.push(`${successIcon} ${uiTheme.fg("success", `${items.length} ${plural}`)}`);
373
+ } else {
374
+ const rawType = pathParts.at(-2) ?? "";
375
+ const name = resourceName ?? "";
376
+ const label = rawType && name ? `${humanizeResourceType(rawType)} '${name}'` : displayPath;
377
+ resultLines.push(`${successIcon} ${uiTheme.fg("success", `Loaded ${label}`)}`);
378
+ }
379
+ } else if (status >= 200 && status < 300) {
380
+ resultLines.push(`${successIcon} ${uiTheme.fg("success", "OK")}`);
381
+ }
382
+
383
+ if (resultLines.length > 0) addSection("Result", resultLines);
355
384
  }
356
385
 
357
386
  // --- 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 };
@@ -817,13 +822,6 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
817
822
  // Label changes help the model echo type names in its response (Finding 13).
818
823
  // INTERACTION EFFECT: this change + TCP protocol label are synergistic (Finding 31).
819
824
  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
825
  let resourceLabel: string;
828
826
  if (params.method === "POST") {
829
827
  const rawType = pathSegs.at(-1) ?? "";
@@ -835,6 +833,9 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
835
833
  const rawType = pathSegs.at(-2) ?? "";
836
834
  resourceLabel = name && rawType ? `${humanizeResourceType(rawType)} ${name}` : resolvedPath;
837
835
  }
836
+ detail.mutationVerb = verb as "Created" | "Updated" | "Deleted";
837
+ detail.resourceLabel = resourceLabel;
838
+
838
839
  // POST returns the full resource; PUT/DELETE return {}.
839
840
  // Only claim response contains the resource for POST to avoid misleading the model.
840
841
  const resourceHint =