@f5xc-salesdemos/xcsh 19.14.3 → 19.15.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/extensibility/extensions/loader.ts +6 -0
- package/src/extensibility/extensions/types.ts +4 -0
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/profile-collectors.ts +11 -1
- package/src/internal-urls/user-profile.ts +53 -0
- package/src/modes/interactive-mode.ts +2 -2
- package/src/prompts/tools/xcsh-api.md +22 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.15.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.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "19.15.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.15.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.15.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.15.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "19.15.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "19.15.0",
|
|
59
59
|
"@sinclair/typebox": "^0.34",
|
|
60
60
|
"@xterm/headless": "^6.0",
|
|
61
61
|
"ajv": "^8.20",
|
|
@@ -15,6 +15,8 @@ import { loadCapability } from "../../discovery";
|
|
|
15
15
|
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
16
16
|
import type { ExecOptions } from "../../exec/exec";
|
|
17
17
|
import { execCommand } from "../../exec/exec";
|
|
18
|
+
import type { ProfileCollector } from "../../internal-urls/profile-collectors";
|
|
19
|
+
import { registerProfileCollector as registerProfileCollectorCore } from "../../internal-urls/profile-collectors";
|
|
18
20
|
import type { CustomMessage } from "../../session/messages";
|
|
19
21
|
import { EventBus } from "../../utils/event-bus";
|
|
20
22
|
import { getAllPluginExtensionPaths } from "../plugins/loader";
|
|
@@ -181,6 +183,10 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
181
183
|
this.extension.serviceStatuses.set(contribution.name, contribution);
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
registerProfileCollector(collector: ProfileCollector): void {
|
|
187
|
+
registerProfileCollectorCore(collector);
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
getFlag(name: string): boolean | string | undefined {
|
|
185
191
|
if (!this.extension.flags.has(name)) return undefined;
|
|
186
192
|
return this.runtime.flagValues.get(name);
|
|
@@ -35,6 +35,7 @@ import type { ModelRegistry } from "../../config/model-registry";
|
|
|
35
35
|
import type { EditToolDetails } from "../../edit";
|
|
36
36
|
import type { BashResult } from "../../exec/bash-executor";
|
|
37
37
|
import type { ExecOptions, ExecResult } from "../../exec/exec";
|
|
38
|
+
import type { ProfileCollector } from "../../internal-urls/profile-collectors";
|
|
38
39
|
import type { PythonResult } from "../../ipy/executor";
|
|
39
40
|
import type { Theme } from "../../modes/theme/theme";
|
|
40
41
|
import type { CompactionPreparation, CompactionResult } from "../../session/compaction";
|
|
@@ -1081,6 +1082,9 @@ export interface ExtensionAPI {
|
|
|
1081
1082
|
/** Register a service status check for the welcome screen. */
|
|
1082
1083
|
registerServiceStatus(contribution: ServiceStatusContribution): void;
|
|
1083
1084
|
|
|
1085
|
+
/** Register a profile collector that pushes discovered person-data to the user profile. */
|
|
1086
|
+
registerProfileCollector(collector: ProfileCollector): void;
|
|
1087
|
+
|
|
1084
1088
|
// =========================================================================
|
|
1085
1089
|
// Actions
|
|
1086
1090
|
// =========================================================================
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.15.0",
|
|
21
|
+
"commit": "16f2b4bafacdf1eefeebfb12409b529cf1aa3354",
|
|
22
|
+
"shortCommit": "16f2b4b",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.15.0",
|
|
25
|
+
"commitDate": "2026-06-08T02:19:41Z",
|
|
26
|
+
"buildDate": "2026-06-08T02:48:16.108Z",
|
|
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/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/16f2b4bafacdf1eefeebfb12409b529cf1aa3354",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.15.0"
|
|
33
33
|
};
|
|
@@ -52,4 +52,14 @@ const systemCollector: ProfileCollector = {
|
|
|
52
52
|
// Registry
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
const _collectors: ProfileCollector[] = [systemCollector];
|
|
56
|
+
|
|
57
|
+
export const PROFILE_COLLECTORS: readonly ProfileCollector[] = _collectors;
|
|
58
|
+
|
|
59
|
+
export function registerProfileCollector(collector: ProfileCollector): void {
|
|
60
|
+
if (_collectors.some(c => c.id === collector.id)) {
|
|
61
|
+
logger.warn(`Profile collector '${collector.id}' already registered, skipping`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
_collectors.push(collector);
|
|
65
|
+
}
|
|
@@ -67,6 +67,8 @@ export interface UserProfile {
|
|
|
67
67
|
observations?: UserProfileObservation[];
|
|
68
68
|
sources?: { github?: string; system?: string; conversation?: string };
|
|
69
69
|
updatedAt?: string;
|
|
70
|
+
/** Tracks which collector ID authoritatively owns each top-level field. */
|
|
71
|
+
_fieldOwnership?: Record<string, string>;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
const PROFILE_PATH = path.join(os.homedir(), ".xcsh", "user-profile.json");
|
|
@@ -142,6 +144,57 @@ export async function seedProfile(): Promise<UserProfile> {
|
|
|
142
144
|
return profile;
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
const META_FIELDS = new Set(["sources", "observations", "updatedAt", "_fieldOwnership"]);
|
|
148
|
+
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
149
|
+
|
|
150
|
+
export function reconcileProfile(target: UserProfile, source: Partial<UserProfile>, sourceId: string): void {
|
|
151
|
+
const ownership = target._fieldOwnership ?? {};
|
|
152
|
+
|
|
153
|
+
for (const [key, value] of Object.entries(source)) {
|
|
154
|
+
if (value === undefined || value === null) continue;
|
|
155
|
+
if (FORBIDDEN_KEYS.has(key)) continue;
|
|
156
|
+
const k = key as keyof UserProfile;
|
|
157
|
+
if (META_FIELDS.has(k)) continue;
|
|
158
|
+
|
|
159
|
+
const currentOwner = ownership[k];
|
|
160
|
+
|
|
161
|
+
if (currentOwner === "user") continue;
|
|
162
|
+
if (currentOwner && currentOwner !== sourceId) continue;
|
|
163
|
+
if (!currentOwner && target[k] !== undefined && target[k] !== null) continue;
|
|
164
|
+
|
|
165
|
+
Object.defineProperty(target, k, { value, writable: true, enumerable: true, configurable: true });
|
|
166
|
+
if (!currentOwner) {
|
|
167
|
+
Object.defineProperty(ownership, k, { value: sourceId, writable: true, enumerable: true, configurable: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
target._fieldOwnership = ownership;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function reconcileFromCollectors(): Promise<UserProfile> {
|
|
175
|
+
const profile = await loadProfile();
|
|
176
|
+
if (!profile.sources) profile.sources = {};
|
|
177
|
+
|
|
178
|
+
for (const collector of PROFILE_COLLECTORS) {
|
|
179
|
+
try {
|
|
180
|
+
const isAvailable = await collector.available();
|
|
181
|
+
if (!isAvailable) {
|
|
182
|
+
logger.debug(`Profile collector '${collector.id}' not available, skipping`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const partial = await collector.collect();
|
|
186
|
+
reconcileProfile(profile, partial, collector.id);
|
|
187
|
+
(profile.sources as Record<string, string>)[collector.id] = new Date().toISOString();
|
|
188
|
+
logger.debug(`Profile collector '${collector.id}' reconciled`);
|
|
189
|
+
} catch (err: unknown) {
|
|
190
|
+
logger.debug(`Profile collector '${collector.id}' failed`, { error: err });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await saveProfile(profile);
|
|
195
|
+
return profile;
|
|
196
|
+
}
|
|
197
|
+
|
|
145
198
|
function formatAddress(addr: NonNullable<UserProfile["address"]>): string {
|
|
146
199
|
const parts: string[] = [];
|
|
147
200
|
if (addr.streetAddress) parts.push(addr.streetAddress);
|
|
@@ -29,7 +29,7 @@ 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
31
|
import { seedComputerProfile } from "../internal-urls/computer-profile";
|
|
32
|
-
import {
|
|
32
|
+
import { reconcileFromCollectors } from "../internal-urls/user-profile";
|
|
33
33
|
import { renameApprovedPlanFile } from "../plan-mode/approved-plan";
|
|
34
34
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
35
35
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
@@ -321,7 +321,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
321
321
|
);
|
|
322
322
|
|
|
323
323
|
// Refresh user profile in background — fire and forget
|
|
324
|
-
|
|
324
|
+
reconcileFromCollectors().catch(err => logger.warn("Background profile refresh failed", { error: String(err) }));
|
|
325
325
|
// Refresh computer profile in background — fire and forget
|
|
326
326
|
seedComputerProfile().catch(err =>
|
|
327
327
|
logger.warn("Background computer profile refresh failed", { error: String(err) }),
|
|
@@ -55,3 +55,25 @@ For HTTPS: replace `"http": {"port": 80}` with `"https_auto_cert": {"http_redire
|
|
|
55
55
|
- `policers` / "policer": `{"metadata":{"name":"<n>","namespace":"<ns>"},"spec":{"burst_size":<int>,"committed_information_rate":<int>}}` — network-level byte/Mbps limiting
|
|
56
56
|
- `rate_limiters` / "rate limiter": `{"metadata":{"name":"<n>","namespace":"<ns>"},"spec":{"burst_size":<int>,"committed_information_rate":<int>}}` — HTTP request-level limiting (rps)
|
|
57
57
|
- `rate_limiter_policys` / "rate limiter policy": requires existing `rate_limiter` reference; use `{"metadata":{"name":"<n>","namespace":"<ns>"},"spec":{"any_server":{},"rules":[{"metadata":{"name":"r"},"spec":{"any_client":{},"any_ip":{},"rate_limiter":{"namespace":"<ns>","name":"<rl-name>"}}}]}}`
|
|
58
|
+
|
|
59
|
+
**SecureMesh Site v2 (`securemesh_site_v2s`) — system namespace only** — POST to `/api/config/namespaces/system/securemesh_site_v2s`. Base payload with all 12 oneOf groups set to defaults:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{"metadata":{"name":"<n>","namespace":"system"},"spec":{"azure":{"not_managed":{"node_list":[]}},"disable_ha":{},"block_all_services":{},"no_network_policy":{},"no_forward_proxy":{},"f5_proxy":{},"no_proxy_bypass":{},"logs_streaming_disabled":{},"no_s2s_connectivity_sli":{},"no_s2s_connectivity_slo":{},"disable_url_categorization":{},"disable_management_network":{}}}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each of the 12 oneOf groups has two or three mutually exclusive choices — pick exactly one per group:
|
|
66
|
+
|
|
67
|
+
|Group|Options (pick one)|Notes|
|
|
68
|
+
|---|---|---|
|
|
69
|
+
|node_ha|`"disable_ha":{}` OR `"enable_ha":{}`||
|
|
70
|
+
|blocked_services|`"block_all_services":{}` OR `"blocked_services":{"service_list":[{"service":"HTTP"}]}`||
|
|
71
|
+
|network_policy|`"no_network_policy":{}` OR `"active_enhanced_firewall_policies":{"active_enhanced_firewall_policies":[{"name":"<n>","namespace":"<ns>"}]}`|prereq: create `enhanced_firewall_policys` with `spec:{}` first|
|
|
72
|
+
|forward_proxy|`"no_forward_proxy":{}` OR `"active_forward_proxy_policies":{"active_forward_proxy_policies":[{"name":"<n>","namespace":"<ns>"}]}`|prereq: create `forward_proxy_policys` with `spec:{"drp_http_connect":{},"allow_all":{}}` — drp_http_connect **REQUIRED**|
|
|
73
|
+
|enterprise_proxy|`"f5_proxy":{}` OR `"custom_proxy":{"http_proxy":"http://proxy:8080","https_proxy":"http://proxy:8080"}`||
|
|
74
|
+
|proxy_bypass|`"no_proxy_bypass":{}` OR `"custom_proxy_bypass":{"bypass_list":["10.0.0.0/8"]}`||
|
|
75
|
+
|logs_receiver|`"logs_streaming_disabled":{}` OR `"log_receiver":{"name":"<n>","namespace":"<ns>"}`|prereq: create `global_log_receivers` with `spec:{"request_logs":{},"http_receiver":{"uri":"http://logs:8080","no_tls":{},"disable_authentication":{}}}`|
|
|
76
|
+
|s2s_sli|`"no_s2s_connectivity_sli":{}` OR `"dc_cluster_group_sli":{"name":"<n>","namespace":"system"}`|prereq: create `dc_cluster_groups` in system ns with `spec:{}`|
|
|
77
|
+
|s2s_slo|`"no_s2s_connectivity_slo":{}` OR `"dc_cluster_group_slo":{"name":"<n>","namespace":"system"}` OR `"site_mesh_group_on_slo":{"name":"<n>","namespace":"system"}`|dc_cluster_group prereq same as above; site_mesh_group prereq: `spec:{"type":"SITE_MESH_GROUP_TYPE_FULL_MESH","tunnel_type":"SITE_TO_SITE_TUNNEL_IPSEC","full_mesh":{"data_plane_mesh":{}},"bfd_disabled":{}}` — bfd_disabled **REQUIRED**|
|
|
78
|
+
|url_categorization|`"disable_url_categorization":{}` OR `"enable_url_categorization":{}`||
|
|
79
|
+
|management_network|`"disable_management_network":{}` OR `"enable_management_network":{}`||
|