@f5xc-salesdemos/xcsh 18.78.0 → 18.80.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.
|
|
4
|
+
"version": "18.80.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.80.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "18.80.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "18.80.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "18.80.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "18.80.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "18.80.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34",
|
|
58
58
|
"@xterm/headless": "^6.0",
|
|
59
59
|
"ajv": "^8.18",
|
|
@@ -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.80.0",
|
|
21
|
+
"commit": "7c19c50d50c5dacf1cb9e16ec599044ee32e1645",
|
|
22
|
+
"shortCommit": "7c19c50",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
24
|
+
"tag": "v18.80.0",
|
|
25
|
+
"commitDate": "2026-05-25T05:57:39Z",
|
|
26
|
+
"buildDate": "2026-05-25T06:19:42.244Z",
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/7c19c50d50c5dacf1cb9e16ec599044ee32e1645",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.80.0"
|
|
33
33
|
};
|
|
@@ -376,6 +376,18 @@ export class RpcClient {
|
|
|
376
376
|
return this.#getData<{ models: ModelInfo[] }>(response).models;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Get integration health status (F5 XC Context, GitLab, GitHub, Salesforce, Azure, AWS, Google Cloud).
|
|
381
|
+
*/
|
|
382
|
+
async getIntegrations(): Promise<{
|
|
383
|
+
version: string;
|
|
384
|
+
model: { state: "no_provider" | "connected" | "auth_error"; provider?: string; latencyMs?: number };
|
|
385
|
+
services: Array<{ name: string; state: "connected" | "unauthenticated" | "unavailable"; hint?: string }>;
|
|
386
|
+
}> {
|
|
387
|
+
const response = await this.#send({ type: "get_integrations" });
|
|
388
|
+
return this.#getData(response);
|
|
389
|
+
}
|
|
390
|
+
|
|
379
391
|
/**
|
|
380
392
|
* Set thinking level.
|
|
381
393
|
*/
|
package/src/tools/xcsh-api.ts
CHANGED
|
@@ -6,6 +6,53 @@ 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
8
|
|
|
9
|
+
// Namespace filtering driven by x-f5xc-namespace-profile from enriched API specs.
|
|
10
|
+
|
|
11
|
+
type NamespaceType = "system" | "shared" | "default" | "custom";
|
|
12
|
+
|
|
13
|
+
function namespaceTypeOf(namespaceName: string): NamespaceType {
|
|
14
|
+
if (namespaceName === "system") return "system";
|
|
15
|
+
if (namespaceName === "shared") return "shared";
|
|
16
|
+
if (namespaceName === "default") return "default";
|
|
17
|
+
if (namespaceName.startsWith("ves-io-")) return "system";
|
|
18
|
+
return "custom";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default allowed namespace types for multi-namespace batch queries.
|
|
23
|
+
* Matches the default_profile.constraint.allowed from namespace_profile.yaml.
|
|
24
|
+
* When a batch spans multiple resource types we cannot use a single resource's
|
|
25
|
+
* namespace profile, so this safe default excludes system namespaces.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_ALLOWED_NS_TYPES: ReadonlySet<NamespaceType> = new Set<NamespaceType>(["custom", "default", "shared"]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Look up allowed namespace types from the embedded API spec data.
|
|
31
|
+
* Returns the namespace profile constraint if the spec has x-f5xc-namespace-profile,
|
|
32
|
+
* otherwise returns the default allowed types.
|
|
33
|
+
*/
|
|
34
|
+
function loadAllowedNamespaceTypes(domain?: string): ReadonlySet<NamespaceType> {
|
|
35
|
+
if (!domain) return DEFAULT_ALLOWED_NS_TYPES;
|
|
36
|
+
try {
|
|
37
|
+
const mod = require("../internal-urls/api-spec-index.generated") as {
|
|
38
|
+
API_SPEC_DATA?: Readonly<
|
|
39
|
+
Record<
|
|
40
|
+
string,
|
|
41
|
+
{ info?: { "x-f5xc-namespace-profile"?: { constraint?: { allowed?: string[] } } }; [k: string]: unknown }
|
|
42
|
+
>
|
|
43
|
+
>;
|
|
44
|
+
};
|
|
45
|
+
const spec = mod.API_SPEC_DATA?.[domain];
|
|
46
|
+
const allowed = spec?.info?.["x-f5xc-namespace-profile"]?.constraint?.allowed;
|
|
47
|
+
if (Array.isArray(allowed) && allowed.length > 0) {
|
|
48
|
+
return new Set(allowed as NamespaceType[]);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// Spec data unavailable — use default
|
|
52
|
+
}
|
|
53
|
+
return DEFAULT_ALLOWED_NS_TYPES;
|
|
54
|
+
}
|
|
55
|
+
|
|
9
56
|
const xcshApiSchema = Type.Object({
|
|
10
57
|
method: Type.Union(
|
|
11
58
|
[Type.Literal("GET"), Type.Literal("POST"), Type.Literal("PUT"), Type.Literal("PATCH"), Type.Literal("DELETE")],
|
|
@@ -84,6 +131,7 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
84
131
|
#contextEnv: ContextEnv;
|
|
85
132
|
#lastApiBase = "";
|
|
86
133
|
#listablePathsCache: string[] | null = null;
|
|
134
|
+
#autoExpandPathsCache: string[] | null = null;
|
|
87
135
|
#expandedNamespaces = new Set<string>();
|
|
88
136
|
|
|
89
137
|
constructor(session: ToolSession) {
|
|
@@ -131,7 +179,11 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
131
179
|
const CONFIG_PREFIX = "/api/config/namespaces/{namespace}/";
|
|
132
180
|
// Only include app/security types (keyword filter). Reduces batch from ~136 to ~42 paths,
|
|
133
181
|
// cutting expansion time by ~3× and eliminating infrastructure noise from the response.
|
|
182
|
+
// Healthcheck is included in batch content (for HC labels) but NOT in the auto-expand
|
|
183
|
+
// trigger list — direct GET to /healthchecks should not trigger a full namespace expansion.
|
|
134
184
|
const APP_KW =
|
|
185
|
+
/loadbalancer|pool|firewall|healthcheck|_policys|setting|type|mitigation|identification|network|route|host|definition|rate_limiter|prefix_set|cdn|waf|api_/i;
|
|
186
|
+
const AUTO_EXPAND_KW =
|
|
135
187
|
/loadbalancer|pool|firewall|_policys|setting|type|mitigation|identification|network|route|host|definition|rate_limiter|prefix_set|cdn|waf|api_/i;
|
|
136
188
|
const META_EXCL = /policy_set|policy_rule|data_polic/i;
|
|
137
189
|
for (const summary of summaries) {
|
|
@@ -151,6 +203,11 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
151
203
|
}
|
|
152
204
|
}
|
|
153
205
|
}
|
|
206
|
+
// Build separate auto-expand trigger list (excludes healthcheck)
|
|
207
|
+
this.#autoExpandPathsCache = paths.filter(p => {
|
|
208
|
+
const last = p.split("/").filter(Boolean).at(-1) ?? "";
|
|
209
|
+
return AUTO_EXPAND_KW.test(last);
|
|
210
|
+
});
|
|
154
211
|
this.#listablePathsCache = paths;
|
|
155
212
|
return paths;
|
|
156
213
|
} catch {
|
|
@@ -170,7 +227,11 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
170
227
|
const fetchSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
171
228
|
const startMs = performance.now();
|
|
172
229
|
|
|
173
|
-
// Discover all accessible namespaces
|
|
230
|
+
// Discover all accessible namespaces, filtered by spec-driven namespace profile.
|
|
231
|
+
// Multi-resource batch queries use DEFAULT_ALLOWED_NS_TYPES (custom, default, shared)
|
|
232
|
+
// which excludes system namespaces. When x-f5xc-namespace-profile is present in
|
|
233
|
+
// enriched specs, loadAllowedNamespaceTypes() will use the spec's constraint.
|
|
234
|
+
const allowedTypes = loadAllowedNamespaceTypes();
|
|
174
235
|
let allNs: string[] = [];
|
|
175
236
|
try {
|
|
176
237
|
const nsResp = await fetch(`${apiBase}/api/web/namespaces`, {
|
|
@@ -182,13 +243,7 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
182
243
|
const nsData = (await nsResp.json()) as { items?: Array<Record<string, unknown>> };
|
|
183
244
|
allNs = (nsData.items ?? [])
|
|
184
245
|
.map(item => (typeof item.name === "string" ? item.name : null))
|
|
185
|
-
.filter(
|
|
186
|
-
(name): name is string =>
|
|
187
|
-
name !== null &&
|
|
188
|
-
!name.startsWith("ves-io") &&
|
|
189
|
-
!name.startsWith("system") &&
|
|
190
|
-
!name.startsWith("shared"),
|
|
191
|
-
);
|
|
246
|
+
.filter((name): name is string => name !== null && allowedTypes.has(namespaceTypeOf(name)));
|
|
192
247
|
}
|
|
193
248
|
} catch {
|
|
194
249
|
// Fall back to default namespace only
|
|
@@ -412,15 +467,22 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
412
467
|
// Core types get detailed per-resource output with spec summaries.
|
|
413
468
|
// Secondary types get a compact count to reduce batch response noise.
|
|
414
469
|
// Shorter, focused output helps the model find relationship data directly.
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
const
|
|
470
|
+
const getRawTypeName = (r: BatchEntry) => r.path.split("/").pop() ?? r.path;
|
|
471
|
+
// Human-readable type name for display: http_loadbalancers → load balancers, origin_pools → origin pools
|
|
472
|
+
const humanizeType = (raw: string): string =>
|
|
473
|
+
raw
|
|
474
|
+
.replace(/^http_/, "")
|
|
475
|
+
.replace(/_/g, " ")
|
|
476
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
477
|
+
.replace(/([a-z])(balancer|checker)/gi, "$1 $2");
|
|
478
|
+
const coreTypes = relevantData.filter(r => SPEC_TYPES.test(getRawTypeName(r)));
|
|
479
|
+
const secondaryTypes = relevantData.filter(r => !SPEC_TYPES.test(getRawTypeName(r)));
|
|
418
480
|
|
|
419
481
|
if (coreTypes.length > 0) {
|
|
420
482
|
sections.push(`Namespace resource inventory (${coreTypes.length} core types):\n`);
|
|
421
483
|
let idx = 1;
|
|
422
484
|
for (const r of coreTypes) {
|
|
423
|
-
const typeName =
|
|
485
|
+
const typeName = humanizeType(getRawTypeName(r));
|
|
424
486
|
const items = (r.parsed?.items as Array<Record<string, unknown>> | undefined) ?? [];
|
|
425
487
|
if (items.length === 0) {
|
|
426
488
|
sections.push(`${idx}. ${typeName}: ${r.itemCount} item(s)`);
|
|
@@ -439,7 +501,7 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
439
501
|
if (secondaryTypes.length > 0) {
|
|
440
502
|
const secondaryCount = secondaryTypes.reduce((sum, r) => sum + (r.itemCount ?? 0), 0);
|
|
441
503
|
sections.push(
|
|
442
|
-
`\n(+${secondaryTypes.length} other types with ${secondaryCount} items: ${secondaryTypes.map(r =>
|
|
504
|
+
`\n(+${secondaryTypes.length} other types with ${secondaryCount} items: ${secondaryTypes.map(r => humanizeType(getRawTypeName(r))).join(", ")})`,
|
|
443
505
|
);
|
|
444
506
|
}
|
|
445
507
|
|
|
@@ -493,6 +555,15 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
493
555
|
if (spec.monitoring != null) labels.push("monitoring-mode");
|
|
494
556
|
else if (spec.blocking != null) labels.push("blocking-mode");
|
|
495
557
|
}
|
|
558
|
+
if (/healthcheck/i.test(rType)) {
|
|
559
|
+
if (spec.http_health_check != null) {
|
|
560
|
+
labels.push("http");
|
|
561
|
+
const httpHC = spec.http_health_check as Record<string, unknown>;
|
|
562
|
+
if (typeof httpHC.path === "string") labels.push(`path=${httpHC.path}`);
|
|
563
|
+
} else if (spec.tcp_health_check != null) {
|
|
564
|
+
labels.push("tcp");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
496
567
|
if (labels.length > 0) {
|
|
497
568
|
summaryLines.push(`${rName}: ${labels.join(", ")}`);
|
|
498
569
|
}
|
|
@@ -595,12 +666,15 @@ export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolD
|
|
|
595
666
|
// File-based cache in #executeBatch prevents redundant API calls across sessions.
|
|
596
667
|
if (params.method === "GET" && !params.payload) {
|
|
597
668
|
const listablePaths = this.#loadListablePaths();
|
|
598
|
-
|
|
669
|
+
// Use the auto-expand trigger list (excludes healthcheck) to decide WHETHER to expand.
|
|
670
|
+
// The batch itself uses the full listablePaths (includes healthcheck) for content.
|
|
671
|
+
const triggerPaths = this.#autoExpandPathsCache ?? listablePaths;
|
|
672
|
+
if (triggerPaths.length > 0) {
|
|
599
673
|
const normalized = params.path.replace(
|
|
600
674
|
/\/api\/config\/namespaces\/[^/]+\//,
|
|
601
675
|
"/api/config/namespaces/{namespace}/",
|
|
602
676
|
);
|
|
603
|
-
if (
|
|
677
|
+
if (triggerPaths.includes(params.path) || triggerPaths.includes(normalized)) {
|
|
604
678
|
const nsMatch = params.path.match(/\/api\/config\/namespaces\/([^/]+)\//);
|
|
605
679
|
const ns = params.params?.namespace ?? (nsMatch?.[1] && nsMatch[1] !== "{namespace}" ? nsMatch[1] : "");
|
|
606
680
|
if (ns && !this.#expandedNamespaces.has(ns)) {
|