@f5-sales-demo/xcsh 19.53.0 → 19.53.2

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": "@f5-sales-demo/xcsh",
4
- "version": "19.53.0",
4
+ "version": "19.53.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -55,13 +55,13 @@
55
55
  "dependencies": {
56
56
  "@agentclientprotocol/sdk": "0.16.1",
57
57
  "@mozilla/readability": "^0.6",
58
- "@f5-sales-demo/xcsh-stats": "19.53.0",
59
- "@f5-sales-demo/pi-agent-core": "19.53.0",
60
- "@f5-sales-demo/pi-ai": "19.53.0",
61
- "@f5-sales-demo/pi-natives": "19.53.0",
62
- "@f5-sales-demo/pi-resource-management": "19.53.0",
63
- "@f5-sales-demo/pi-tui": "19.53.0",
64
- "@f5-sales-demo/pi-utils": "19.53.0",
58
+ "@f5-sales-demo/xcsh-stats": "19.53.2",
59
+ "@f5-sales-demo/pi-agent-core": "19.53.2",
60
+ "@f5-sales-demo/pi-ai": "19.53.2",
61
+ "@f5-sales-demo/pi-natives": "19.53.2",
62
+ "@f5-sales-demo/pi-resource-management": "19.53.2",
63
+ "@f5-sales-demo/pi-tui": "19.53.2",
64
+ "@f5-sales-demo/pi-utils": "19.53.2",
65
65
  "@sinclair/typebox": "^0.34",
66
66
  "@xterm/headless": "^6.0",
67
67
  "ajv": "^8.20",
@@ -0,0 +1,595 @@
1
+ {
2
+ "contractVersion": "1.2.0",
3
+ "schemas": {
4
+ "chat_request": {
5
+ "type": "object",
6
+ "required": ["type", "id", "text", "context", "mode"],
7
+ "properties": {
8
+ "type": {
9
+ "const": "chat_request",
10
+ "type": "string"
11
+ },
12
+ "id": {
13
+ "pattern": "^c-",
14
+ "type": "string"
15
+ },
16
+ "text": {
17
+ "type": "string"
18
+ },
19
+ "context": {
20
+ "anyOf": [
21
+ {
22
+ "type": "null"
23
+ },
24
+ {
25
+ "type": "object",
26
+ "required": ["v", "capturedAt", "tabId", "url", "path", "title", "ax", "api", "truncated"],
27
+ "properties": {
28
+ "v": {
29
+ "const": 1,
30
+ "type": "number"
31
+ },
32
+ "capturedAt": {
33
+ "type": "number"
34
+ },
35
+ "tabId": {
36
+ "type": "number"
37
+ },
38
+ "url": {
39
+ "type": "string"
40
+ },
41
+ "path": {
42
+ "type": "string"
43
+ },
44
+ "title": {
45
+ "type": "string"
46
+ },
47
+ "ax": {
48
+ "anyOf": [
49
+ {
50
+ "type": "null"
51
+ },
52
+ {
53
+ "additionalProperties": true,
54
+ "type": "object",
55
+ "required": ["role"],
56
+ "properties": {
57
+ "role": {
58
+ "type": "string"
59
+ }
60
+ }
61
+ }
62
+ ]
63
+ },
64
+ "api": {
65
+ "anyOf": [
66
+ {
67
+ "type": "null"
68
+ },
69
+ {
70
+ "type": "object",
71
+ "required": ["url", "status", "resourceType", "body", "truncated"],
72
+ "properties": {
73
+ "url": {
74
+ "type": "string"
75
+ },
76
+ "status": {
77
+ "type": "number"
78
+ },
79
+ "resourceType": {
80
+ "anyOf": [
81
+ {
82
+ "type": "string"
83
+ },
84
+ {
85
+ "type": "null"
86
+ }
87
+ ]
88
+ },
89
+ "body": {},
90
+ "truncated": {
91
+ "type": "boolean"
92
+ }
93
+ }
94
+ }
95
+ ]
96
+ },
97
+ "truncated": {
98
+ "type": "boolean"
99
+ }
100
+ }
101
+ }
102
+ ]
103
+ },
104
+ "mode": {
105
+ "anyOf": [
106
+ {
107
+ "const": "educational",
108
+ "type": "string"
109
+ },
110
+ {
111
+ "const": "presentation",
112
+ "type": "string"
113
+ },
114
+ {
115
+ "const": "configuration",
116
+ "type": "string"
117
+ },
118
+ {
119
+ "const": "screenshot",
120
+ "type": "string"
121
+ },
122
+ {
123
+ "const": "annotation",
124
+ "type": "string"
125
+ }
126
+ ]
127
+ },
128
+ "history_hint": {
129
+ "type": "string"
130
+ }
131
+ }
132
+ },
133
+ "chat_stop": {
134
+ "type": "object",
135
+ "required": ["type", "id"],
136
+ "properties": {
137
+ "type": {
138
+ "const": "chat_stop",
139
+ "type": "string"
140
+ },
141
+ "id": {
142
+ "pattern": "^c-",
143
+ "type": "string"
144
+ }
145
+ }
146
+ },
147
+ "chat_delta": {
148
+ "type": "object",
149
+ "required": ["type", "id", "seq", "delta"],
150
+ "properties": {
151
+ "type": {
152
+ "const": "chat_delta",
153
+ "type": "string"
154
+ },
155
+ "id": {
156
+ "pattern": "^c-",
157
+ "type": "string"
158
+ },
159
+ "seq": {
160
+ "type": "number"
161
+ },
162
+ "delta": {
163
+ "type": "string"
164
+ }
165
+ }
166
+ },
167
+ "chat_done": {
168
+ "type": "object",
169
+ "required": ["type", "id"],
170
+ "properties": {
171
+ "type": {
172
+ "const": "chat_done",
173
+ "type": "string"
174
+ },
175
+ "id": {
176
+ "pattern": "^c-",
177
+ "type": "string"
178
+ },
179
+ "references": {
180
+ "type": "array",
181
+ "items": {
182
+ "type": "object",
183
+ "required": ["kind", "title", "url"],
184
+ "properties": {
185
+ "kind": {
186
+ "type": "string"
187
+ },
188
+ "title": {
189
+ "type": "string"
190
+ },
191
+ "url": {
192
+ "type": "string"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ },
199
+ "chat_error": {
200
+ "type": "object",
201
+ "required": ["type", "id", "error"],
202
+ "properties": {
203
+ "type": {
204
+ "const": "chat_error",
205
+ "type": "string"
206
+ },
207
+ "id": {
208
+ "pattern": "^c-",
209
+ "type": "string"
210
+ },
211
+ "error": {
212
+ "type": "string"
213
+ }
214
+ }
215
+ },
216
+ "chat_tool_notice": {
217
+ "type": "object",
218
+ "required": ["type", "id", "tool", "ok"],
219
+ "properties": {
220
+ "type": {
221
+ "const": "chat_tool_notice",
222
+ "type": "string"
223
+ },
224
+ "id": {
225
+ "pattern": "^c-",
226
+ "type": "string"
227
+ },
228
+ "tool": {
229
+ "type": "string"
230
+ },
231
+ "ok": {
232
+ "type": "boolean"
233
+ },
234
+ "detail": {
235
+ "type": "string"
236
+ }
237
+ }
238
+ },
239
+ "page_context_snapshot": {
240
+ "type": "object",
241
+ "required": ["v", "capturedAt", "tabId", "url", "path", "title", "ax", "api", "truncated"],
242
+ "properties": {
243
+ "v": {
244
+ "const": 1,
245
+ "type": "number"
246
+ },
247
+ "capturedAt": {
248
+ "type": "number"
249
+ },
250
+ "tabId": {
251
+ "type": "number"
252
+ },
253
+ "url": {
254
+ "type": "string"
255
+ },
256
+ "path": {
257
+ "type": "string"
258
+ },
259
+ "title": {
260
+ "type": "string"
261
+ },
262
+ "ax": {
263
+ "anyOf": [
264
+ {
265
+ "type": "null"
266
+ },
267
+ {
268
+ "additionalProperties": true,
269
+ "type": "object",
270
+ "required": ["role"],
271
+ "properties": {
272
+ "role": {
273
+ "type": "string"
274
+ }
275
+ }
276
+ }
277
+ ]
278
+ },
279
+ "api": {
280
+ "anyOf": [
281
+ {
282
+ "type": "null"
283
+ },
284
+ {
285
+ "type": "object",
286
+ "required": ["url", "status", "resourceType", "body", "truncated"],
287
+ "properties": {
288
+ "url": {
289
+ "type": "string"
290
+ },
291
+ "status": {
292
+ "type": "number"
293
+ },
294
+ "resourceType": {
295
+ "anyOf": [
296
+ {
297
+ "type": "string"
298
+ },
299
+ {
300
+ "type": "null"
301
+ }
302
+ ]
303
+ },
304
+ "body": {},
305
+ "truncated": {
306
+ "type": "boolean"
307
+ }
308
+ }
309
+ }
310
+ ]
311
+ },
312
+ "truncated": {
313
+ "type": "boolean"
314
+ }
315
+ }
316
+ }
317
+ },
318
+ "examples": {
319
+ "valid": {
320
+ "page_context_snapshot": {
321
+ "v": 1,
322
+ "capturedAt": 1719000000000,
323
+ "tabId": 7,
324
+ "url": "https://acme.console.ves.volterra.io/web/namespaces/default/http_loadbalancers/lb1",
325
+ "path": "/web/namespaces/default/http_loadbalancers/lb1",
326
+ "title": "lb1 — Distributed Cloud",
327
+ "ax": {
328
+ "role": "WebArea",
329
+ "name": "lb1",
330
+ "children": [
331
+ {
332
+ "role": "button",
333
+ "name": "Edit",
334
+ "ref": "e12"
335
+ }
336
+ ]
337
+ },
338
+ "api": {
339
+ "url": "/api/config/namespaces/default/http_loadbalancers/lb1",
340
+ "status": 200,
341
+ "resourceType": "http_loadbalancers",
342
+ "body": {
343
+ "metadata": {
344
+ "name": "lb1"
345
+ },
346
+ "spec": {
347
+ "domains": ["lb1.example.com"]
348
+ }
349
+ },
350
+ "truncated": false
351
+ },
352
+ "truncated": false
353
+ },
354
+ "chat_request": {
355
+ "type": "chat_request",
356
+ "id": "c-1111",
357
+ "text": "What does this load balancer do?",
358
+ "context": {
359
+ "v": 1,
360
+ "capturedAt": 1719000000000,
361
+ "tabId": 7,
362
+ "url": "https://acme.console.ves.volterra.io/web/namespaces/default/http_loadbalancers/lb1",
363
+ "path": "/web/namespaces/default/http_loadbalancers/lb1",
364
+ "title": "lb1 — Distributed Cloud",
365
+ "ax": {
366
+ "role": "WebArea",
367
+ "name": "lb1",
368
+ "children": [
369
+ {
370
+ "role": "button",
371
+ "name": "Edit",
372
+ "ref": "e12"
373
+ }
374
+ ]
375
+ },
376
+ "api": {
377
+ "url": "/api/config/namespaces/default/http_loadbalancers/lb1",
378
+ "status": 200,
379
+ "resourceType": "http_loadbalancers",
380
+ "body": {
381
+ "metadata": {
382
+ "name": "lb1"
383
+ },
384
+ "spec": {
385
+ "domains": ["lb1.example.com"]
386
+ }
387
+ },
388
+ "truncated": false
389
+ },
390
+ "truncated": false
391
+ },
392
+ "mode": "educational",
393
+ "history_hint": "conv-1"
394
+ },
395
+ "chat_request_no_context": {
396
+ "type": "chat_request",
397
+ "id": "c-2222",
398
+ "text": "help me build a WAF policy",
399
+ "context": null,
400
+ "mode": "configuration"
401
+ },
402
+ "chat_stop": {
403
+ "type": "chat_stop",
404
+ "id": "c-1111"
405
+ },
406
+ "chat_delta": {
407
+ "type": "chat_delta",
408
+ "id": "c-1111",
409
+ "seq": 0,
410
+ "delta": "This LB "
411
+ },
412
+ "chat_delta_1": {
413
+ "type": "chat_delta",
414
+ "id": "c-1111",
415
+ "seq": 1,
416
+ "delta": "routes traffic."
417
+ },
418
+ "chat_done": {
419
+ "type": "chat_done",
420
+ "id": "c-1111",
421
+ "references": [
422
+ {
423
+ "kind": "doc",
424
+ "title": "HTTP LB",
425
+ "url": "https://docs.cloud.f5.com/docs/how-to"
426
+ }
427
+ ]
428
+ },
429
+ "chat_done_no_refs": {
430
+ "type": "chat_done",
431
+ "id": "c-1111"
432
+ },
433
+ "chat_error": {
434
+ "type": "chat_error",
435
+ "id": "c-1111",
436
+ "error": "HTTP 403 forbidden"
437
+ },
438
+ "chat_tool_notice": {
439
+ "type": "chat_tool_notice",
440
+ "id": "c-1111",
441
+ "tool": "navigate",
442
+ "ok": true
443
+ }
444
+ },
445
+ "invalid": [
446
+ {
447
+ "schema": "chat_request",
448
+ "why": "id missing c- prefix",
449
+ "value": {
450
+ "type": "chat_request",
451
+ "id": "x-1111",
452
+ "text": "What does this load balancer do?",
453
+ "context": {
454
+ "v": 1,
455
+ "capturedAt": 1719000000000,
456
+ "tabId": 7,
457
+ "url": "https://acme.console.ves.volterra.io/web/namespaces/default/http_loadbalancers/lb1",
458
+ "path": "/web/namespaces/default/http_loadbalancers/lb1",
459
+ "title": "lb1 — Distributed Cloud",
460
+ "ax": {
461
+ "role": "WebArea",
462
+ "name": "lb1",
463
+ "children": [
464
+ {
465
+ "role": "button",
466
+ "name": "Edit",
467
+ "ref": "e12"
468
+ }
469
+ ]
470
+ },
471
+ "api": {
472
+ "url": "/api/config/namespaces/default/http_loadbalancers/lb1",
473
+ "status": 200,
474
+ "resourceType": "http_loadbalancers",
475
+ "body": {
476
+ "metadata": {
477
+ "name": "lb1"
478
+ },
479
+ "spec": {
480
+ "domains": ["lb1.example.com"]
481
+ }
482
+ },
483
+ "truncated": false
484
+ },
485
+ "truncated": false
486
+ },
487
+ "mode": "educational",
488
+ "history_hint": "conv-1"
489
+ }
490
+ },
491
+ {
492
+ "schema": "chat_request",
493
+ "why": "unknown mode",
494
+ "value": {
495
+ "type": "chat_request",
496
+ "id": "c-1111",
497
+ "text": "What does this load balancer do?",
498
+ "context": {
499
+ "v": 1,
500
+ "capturedAt": 1719000000000,
501
+ "tabId": 7,
502
+ "url": "https://acme.console.ves.volterra.io/web/namespaces/default/http_loadbalancers/lb1",
503
+ "path": "/web/namespaces/default/http_loadbalancers/lb1",
504
+ "title": "lb1 — Distributed Cloud",
505
+ "ax": {
506
+ "role": "WebArea",
507
+ "name": "lb1",
508
+ "children": [
509
+ {
510
+ "role": "button",
511
+ "name": "Edit",
512
+ "ref": "e12"
513
+ }
514
+ ]
515
+ },
516
+ "api": {
517
+ "url": "/api/config/namespaces/default/http_loadbalancers/lb1",
518
+ "status": 200,
519
+ "resourceType": "http_loadbalancers",
520
+ "body": {
521
+ "metadata": {
522
+ "name": "lb1"
523
+ },
524
+ "spec": {
525
+ "domains": ["lb1.example.com"]
526
+ }
527
+ },
528
+ "truncated": false
529
+ },
530
+ "truncated": false
531
+ },
532
+ "mode": "wizard",
533
+ "history_hint": "conv-1"
534
+ }
535
+ },
536
+ {
537
+ "schema": "chat_request",
538
+ "why": "missing text",
539
+ "value": {
540
+ "type": "chat_request",
541
+ "id": "c-1",
542
+ "context": null,
543
+ "mode": "educational"
544
+ }
545
+ },
546
+ {
547
+ "schema": "chat_delta",
548
+ "why": "missing seq",
549
+ "value": {
550
+ "type": "chat_delta",
551
+ "id": "c-1",
552
+ "delta": "x"
553
+ }
554
+ },
555
+ {
556
+ "schema": "page_context_snapshot",
557
+ "why": "wrong version",
558
+ "value": {
559
+ "v": 2,
560
+ "capturedAt": 1719000000000,
561
+ "tabId": 7,
562
+ "url": "https://acme.console.ves.volterra.io/web/namespaces/default/http_loadbalancers/lb1",
563
+ "path": "/web/namespaces/default/http_loadbalancers/lb1",
564
+ "title": "lb1 — Distributed Cloud",
565
+ "ax": {
566
+ "role": "WebArea",
567
+ "name": "lb1",
568
+ "children": [
569
+ {
570
+ "role": "button",
571
+ "name": "Edit",
572
+ "ref": "e12"
573
+ }
574
+ ]
575
+ },
576
+ "api": {
577
+ "url": "/api/config/namespaces/default/http_loadbalancers/lb1",
578
+ "status": 200,
579
+ "resourceType": "http_loadbalancers",
580
+ "body": {
581
+ "metadata": {
582
+ "name": "lb1"
583
+ },
584
+ "spec": {
585
+ "domains": ["lb1.example.com"]
586
+ }
587
+ },
588
+ "truncated": false
589
+ },
590
+ "truncated": false
591
+ }
592
+ }
593
+ ]
594
+ }
595
+ }
@@ -24,6 +24,7 @@ export class ChatHandler {
24
24
  #server: BridgeServer;
25
25
  #session: AgentSession;
26
26
  #activeChats = new Map<string, ActiveChat>();
27
+ #activeHistoryHint: string | undefined;
27
28
 
28
29
  constructor(server: BridgeServer, session: AgentSession) {
29
30
  this.#server = server;
@@ -53,6 +54,11 @@ export class ChatHandler {
53
54
  return;
54
55
  }
55
56
 
57
+ if (req.history_hint && req.history_hint !== this.#activeHistoryHint) {
58
+ this.#session.agent.replaceMessages([]);
59
+ this.#activeHistoryHint = req.history_hint;
60
+ }
61
+
56
62
  const chat: ActiveChat = { id, seq: 0, terminalSent: false, unsubscribe: () => {} };
57
63
  this.#activeChats.set(id, chat);
58
64
 
@@ -153,7 +159,9 @@ export function composeChatPrompt(text: string, context: PageContextSnapshot | n
153
159
  parts.push(`Title: ${context.title}`);
154
160
 
155
161
  if (context.api) {
156
- parts.push(`API resource (${context.api.resourceType}, status ${context.api.status}): ${context.api.url}`);
162
+ parts.push(
163
+ `API resource (${context.api.resourceType ?? "unknown"}, status ${context.api.status}): ${context.api.url}`,
164
+ );
157
165
  if (context.api.body) {
158
166
  const body =
159
167
  typeof context.api.body === "string" ? context.api.body : JSON.stringify(context.api.body, null, 2);
@@ -181,15 +189,48 @@ export function composeChatPrompt(text: string, context: PageContextSnapshot | n
181
189
  return parts.join("\n");
182
190
  }
183
191
 
192
+ export function classifyReferenceKind(url: string): "doc" | "console" {
193
+ try {
194
+ const parsed = new URL(url);
195
+ if (/\.console\.ves\.volterra\.io$/.test(parsed.hostname)) return "console";
196
+ if (parsed.hostname === "docs.cloud.f5.com" || parsed.pathname.startsWith("/docs")) return "doc";
197
+ } catch {
198
+ /* malformed URL — default to doc */
199
+ }
200
+ return "doc";
201
+ }
202
+
203
+ function titleFromUrl(url: string): string {
204
+ try {
205
+ const parsed = new URL(url);
206
+ const segments = parsed.pathname.split("/").filter(Boolean);
207
+ return segments.length > 0 ? segments[segments.length - 1] : parsed.hostname;
208
+ } catch {
209
+ return url;
210
+ }
211
+ }
212
+
184
213
  function extractReferences(msg: AssistantMessage): ChatReference[] {
185
214
  const refs: ChatReference[] = [];
215
+ const seen = new Set<string>();
216
+
186
217
  for (const block of msg.content) {
187
218
  if (block.type !== "text") continue;
188
- const urlRegex = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
189
- for (let match = urlRegex.exec(block.text); match !== null; match = urlRegex.exec(block.text)) {
219
+
220
+ const mdLinkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
221
+ for (let match = mdLinkRegex.exec(block.text); match !== null; match = mdLinkRegex.exec(block.text)) {
190
222
  const [, title, url] = match;
191
- const kind = url.includes("docs.cloud.f5.com") || url.includes("docs.") ? "doc" : "console";
192
- refs.push({ kind, title, url });
223
+ if (seen.has(url)) continue;
224
+ seen.add(url);
225
+ refs.push({ kind: classifyReferenceKind(url), title, url });
226
+ }
227
+
228
+ const bareUrlRegex = /(?<!\()(https?:\/\/[^\s)>\]]+)/g;
229
+ for (let match = bareUrlRegex.exec(block.text); match !== null; match = bareUrlRegex.exec(block.text)) {
230
+ const url = match[1];
231
+ if (seen.has(url)) continue;
232
+ seen.add(url);
233
+ refs.push({ kind: classifyReferenceKind(url), title: titleFromUrl(url), url });
193
234
  }
194
235
  }
195
236
  return refs;
@@ -10,7 +10,7 @@
10
10
  export interface PageContextApi {
11
11
  url: string;
12
12
  status: number;
13
- resourceType: string;
13
+ resourceType: string | null;
14
14
  body: unknown;
15
15
  truncated: boolean;
16
16
  }
@@ -55,7 +55,7 @@ export interface ChatRequest {
55
55
  text: string;
56
56
  context: PageContextSnapshot | null;
57
57
  mode: InteractionMode;
58
- history_hint: string;
58
+ history_hint?: string;
59
59
  }
60
60
 
61
61
  export interface ChatStop {
@@ -45,7 +45,7 @@ export const BRANDING_DEPRECATIONS = {
45
45
  url: "console.ves.volterra.io",
46
46
  },
47
47
  canonical: {
48
- note: "Tenant-specific. No hardcoded default. Require XCSH_API_URL env var.",
48
+ note: "Tenant-specific. No hardcoded default. Require F5XC_API_URL env var.",
49
49
  },
50
50
  },
51
51
  documentation: {
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.53.0",
21
- "commit": "bd260c74c8205835f4934fc2251fc66d5ef95335",
22
- "shortCommit": "bd260c7",
20
+ "version": "19.53.2",
21
+ "commit": "a6f0d3a5600e8f64e6b4c97ba70ab799c0258291",
22
+ "shortCommit": "a6f0d3a",
23
23
  "branch": "main",
24
- "tag": "v19.53.0",
25
- "commitDate": "2026-06-30T11:39:51Z",
26
- "buildDate": "2026-06-30T12:06:01.035Z",
24
+ "tag": "v19.53.2",
25
+ "commitDate": "2026-06-30T20:14:28Z",
26
+ "buildDate": "2026-06-30T20:38:21.170Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/bd260c74c8205835f4934fc2251fc66d5ef95335",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.53.0"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/a6f0d3a5600e8f64e6b4c97ba70ab799c0258291",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.53.2"
33
33
  };