@f5-sales-demo/xcsh 19.61.5 → 19.62.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": "@f5-sales-demo/xcsh",
4
- "version": "19.61.5",
4
+ "version": "19.62.0",
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",
@@ -56,13 +56,13 @@
56
56
  "dependencies": {
57
57
  "@agentclientprotocol/sdk": "0.16.1",
58
58
  "@mozilla/readability": "^0.6",
59
- "@f5-sales-demo/xcsh-stats": "19.61.5",
60
- "@f5-sales-demo/pi-agent-core": "19.61.5",
61
- "@f5-sales-demo/pi-ai": "19.61.5",
62
- "@f5-sales-demo/pi-natives": "19.61.5",
63
- "@f5-sales-demo/pi-resource-management": "19.61.5",
64
- "@f5-sales-demo/pi-tui": "19.61.5",
65
- "@f5-sales-demo/pi-utils": "19.61.5",
59
+ "@f5-sales-demo/xcsh-stats": "19.62.0",
60
+ "@f5-sales-demo/pi-agent-core": "19.62.0",
61
+ "@f5-sales-demo/pi-ai": "19.62.0",
62
+ "@f5-sales-demo/pi-natives": "19.62.0",
63
+ "@f5-sales-demo/pi-resource-management": "19.62.0",
64
+ "@f5-sales-demo/pi-tui": "19.62.0",
65
+ "@f5-sales-demo/pi-utils": "19.62.0",
66
66
  "@sinclair/typebox": "^0.34",
67
67
  "@xterm/headless": "^6.0",
68
68
  "ajv": "^8.20",
@@ -26,7 +26,7 @@ export interface ExtensionCapabilities {
26
26
 
27
27
  export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
28
28
  "version": "0.1.0",
29
- "contractVersion": "1.5.0",
29
+ "contractVersion": "1.6.0",
30
30
  "multiPortDiscovery": true,
31
31
  "protocol": "tool_request/result",
32
32
  "tools": [
@@ -625,6 +625,26 @@ export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
625
625
  },
626
626
  "flags": {}
627
627
  },
628
+ {
629
+ "name": "diag_activation",
630
+ "summary": "Diagnostic: per-gate tab-activation readiness timings (bridge/worker/page), cold/warm.",
631
+ "category": "read",
632
+ "params": {
633
+ "type": "object",
634
+ "properties": {}
635
+ },
636
+ "flags": {}
637
+ },
638
+ {
639
+ "name": "diag_ttft",
640
+ "summary": "Diagnostic: init→first-token timeline (per-stage ms, total, dominant, cold/warm).",
641
+ "category": "read",
642
+ "params": {
643
+ "type": "object",
644
+ "properties": {}
645
+ },
646
+ "flags": {}
647
+ },
628
648
  {
629
649
  "name": "capture_login_flow",
630
650
  "summary": "Diagnostic: captured login redirect chain annotated with tenant/env (Phase 0b).",
@@ -825,6 +845,6 @@ export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
825
845
  }
826
846
  };
827
847
 
828
- export const EXTENSION_CONTRACT_VERSION = "1.5.0";
848
+ export const EXTENSION_CONTRACT_VERSION = "1.6.0";
829
849
 
830
- export const EXTENSION_TOOL_NAMES: readonly string[] = ["ping","capabilities","reload","debug_exec","detach","set_bridge_port","navigate","login","scroll_to","resize_window","tabs_list","tabs_create","tabs_close","click","click_element","click_xy","type_text","form_input","key_press","select_option","label_select","file_upload","read_ax","get_page_text","query_dom","find","wait_for","assert_text","screenshot","read_console","read_network","diag_suspension","diag_bridges","capture_login_flow","wait_for_api_response","get_page_context","javascript_tool","browser_batch","set_explain_mode","annotate"];
850
+ export const EXTENSION_TOOL_NAMES: readonly string[] = ["ping","capabilities","reload","debug_exec","detach","set_bridge_port","navigate","login","scroll_to","resize_window","tabs_list","tabs_create","tabs_close","click","click_element","click_xy","type_text","form_input","key_press","select_option","label_select","file_upload","read_ax","get_page_text","query_dom","find","wait_for","assert_text","screenshot","read_console","read_network","diag_suspension","diag_bridges","diag_activation","diag_ttft","capture_login_flow","wait_for_api_response","get_page_context","javascript_tool","browser_batch","set_explain_mode","annotate"];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "contractVersion": "1.5.0",
3
+ "contractVersion": "1.6.0",
4
4
  "multiPortDiscovery": true,
5
5
  "protocol": "tool_request/result",
6
6
  "tools": [
@@ -550,6 +550,26 @@
550
550
  },
551
551
  "flags": {}
552
552
  },
553
+ {
554
+ "name": "diag_activation",
555
+ "summary": "Diagnostic: per-gate tab-activation readiness timings (bridge/worker/page), cold/warm.",
556
+ "category": "read",
557
+ "params": {
558
+ "type": "object",
559
+ "properties": {}
560
+ },
561
+ "flags": {}
562
+ },
563
+ {
564
+ "name": "diag_ttft",
565
+ "summary": "Diagnostic: init→first-token timeline (per-stage ms, total, dominant, cold/warm).",
566
+ "category": "read",
567
+ "params": {
568
+ "type": "object",
569
+ "properties": {}
570
+ },
571
+ "flags": {}
572
+ },
553
573
  {
554
574
  "name": "capture_login_flow",
555
575
  "summary": "Diagnostic: captured login redirect chain annotated with tenant/env (Phase 0b).",
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.5.0",
2
+ "contractVersion": "1.6.0",
3
3
  "schemas": {
4
4
  "chat_request": {
5
5
  "type": "object",
@@ -210,6 +210,19 @@
210
210
  },
211
211
  "error": {
212
212
  "type": "string"
213
+ },
214
+ "reason": {
215
+ "anyOf": [
216
+ { "const": "bridge-disconnected", "type": "string" },
217
+ { "const": "bridge-unresponsive", "type": "string" },
218
+ { "const": "no-worker", "type": "string" },
219
+ { "const": "session-busy", "type": "string" },
220
+ { "const": "session-disposed", "type": "string" },
221
+ { "const": "token-expired", "type": "string" },
222
+ { "const": "token-expiring", "type": "string" },
223
+ { "const": "provider-4xx", "type": "string" },
224
+ { "const": "provider-5xx", "type": "string" }
225
+ ]
213
226
  }
214
227
  }
215
228
  },
@@ -433,7 +446,8 @@
433
446
  "chat_error": {
434
447
  "type": "chat_error",
435
448
  "id": "c-1111",
436
- "error": "HTTP 403 forbidden"
449
+ "error": "HTTP 403 forbidden",
450
+ "reason": "provider-4xx"
437
451
  },
438
452
  "chat_tool_notice": {
439
453
  "type": "chat_tool_notice",
@@ -4,6 +4,7 @@ import {
4
4
  type ChatDelta,
5
5
  type ChatDone,
6
6
  type ChatError,
7
+ type ChatErrorReason,
7
8
  type ChatReference,
8
9
  type ChatRequest,
9
10
  type InteractionMode,
@@ -52,7 +53,12 @@ export class ChatHandler {
52
53
 
53
54
  this.#server.onDisconnected(() => {
54
55
  for (const chat of this.#activeChats.values()) {
55
- this.#sendTerminal(chat, { type: "chat_error", id: chat.id, error: "bridge disconnected" });
56
+ this.#sendTerminal(chat, {
57
+ type: "chat_error",
58
+ id: chat.id,
59
+ error: "bridge disconnected",
60
+ reason: "bridge-disconnected",
61
+ });
56
62
  chat.unsubscribe();
57
63
  }
58
64
  this.#activeChats.clear();
@@ -63,7 +69,12 @@ export class ChatHandler {
63
69
  const { id } = req;
64
70
 
65
71
  if (this.#session.isStreaming || this.#activeChats.size > 0) {
66
- this.#server.send({ type: "chat_error", id, error: "session busy" } satisfies ChatError);
72
+ this.#server.send({
73
+ type: "chat_error",
74
+ id,
75
+ error: "session busy",
76
+ reason: "session-busy",
77
+ } satisfies ChatError);
67
78
  return;
68
79
  }
69
80
 
@@ -96,7 +107,7 @@ export class ChatHandler {
96
107
  await this.#session.prompt(prompt, { expandPromptTemplates: false, synthetic: false });
97
108
  } catch (err: unknown) {
98
109
  const message = err instanceof Error ? err.message : "unknown error";
99
- this.#sendTerminal(chat, { type: "chat_error", id, error: message });
110
+ this.#sendTerminal(chat, { type: "chat_error", id, error: message, reason: classifyChatErrorReason(message) });
100
111
  } finally {
101
112
  if (!chat.terminalSent) {
102
113
  this.#sendTerminal(chat, { type: "chat_done", id });
@@ -153,15 +164,22 @@ export class ChatHandler {
153
164
  }
154
165
  } else if (ame.type === "error") {
155
166
  const errorMsg = ame.error?.errorMessage ?? "LLM error";
156
- this.#sendTerminal(chat, { type: "chat_error", id: chat.id, error: errorMsg });
167
+ this.#sendTerminal(chat, {
168
+ type: "chat_error",
169
+ id: chat.id,
170
+ error: errorMsg,
171
+ reason: classifyChatErrorReason(errorMsg),
172
+ });
157
173
  }
158
174
  } else if (event.type === "message_end" && event.message.role === "assistant") {
159
175
  const msg = event.message as AssistantMessage;
160
176
  if (msg.stopReason === "error") {
177
+ const errorMsg = msg.errorMessage ?? "assistant error";
161
178
  this.#sendTerminal(chat, {
162
179
  type: "chat_error",
163
180
  id: chat.id,
164
- error: msg.errorMessage ?? "assistant error",
181
+ error: errorMsg,
182
+ reason: classifyChatErrorReason(errorMsg),
165
183
  });
166
184
  } else {
167
185
  const references = extractReferences(msg);
@@ -194,13 +212,41 @@ export class ChatHandler {
194
212
 
195
213
  dispose(): void {
196
214
  for (const chat of this.#activeChats.values()) {
197
- this.#sendTerminal(chat, { type: "chat_error", id: chat.id, error: "session disposed" });
215
+ this.#sendTerminal(chat, {
216
+ type: "chat_error",
217
+ id: chat.id,
218
+ error: "session disposed",
219
+ reason: "session-disposed",
220
+ });
198
221
  chat.unsubscribe();
199
222
  }
200
223
  this.#activeChats.clear();
201
224
  }
202
225
  }
203
226
 
227
+ /** Best-effort classification of an upstream/provider error message into a
228
+ * ChatErrorReason so the panel can pick a distinct, actionable message. Returns
229
+ * undefined for an unclassified error (the panel then shows the raw error text).
230
+ * Token checks run before the generic 4xx check (a 401 is more useful as an
231
+ * expired-token hint than a bare client error). */
232
+ export function classifyChatErrorReason(message: string): ChatErrorReason | undefined {
233
+ const m = message.toLowerCase();
234
+ if (/\btoken\b[^.]*\b(expir|invalid)|\b(expir|invalid)[^.]*\btoken\b|aws sso|\/context (create|validate)/.test(m)) {
235
+ return "token-expired";
236
+ }
237
+ if (
238
+ /\b5\d\d\b|internal server error|bad gateway|service unavailable|gateway timeout|econnreset|etimedout|socket hang up|network error/.test(
239
+ m,
240
+ )
241
+ ) {
242
+ return "provider-5xx";
243
+ }
244
+ if (/\b4\d\d\b|forbidden|unauthorized|invalid model|bad request|not found|too many requests|rate limit/.test(m)) {
245
+ return "provider-4xx";
246
+ }
247
+ return undefined;
248
+ }
249
+
204
250
  /**
205
251
  * Chrome-extension self-awareness prompt. Injected when xcsh is serving a browser
206
252
  * chat (not the CLI TUI). Tells the LLM it's in a Chrome side panel alongside the
@@ -80,10 +80,30 @@ export interface ChatDone {
80
80
  references?: ChatReference[];
81
81
  }
82
82
 
83
+ /** Machine-readable cause of a terminal chat_error, so the panel can render a
84
+ * distinct, actionable message (and decide whether to auto-recover) instead of a
85
+ * generic failure. Additive/optional on the wire (contract 1.6.0); an omitted
86
+ * reason means an unclassified error (legacy behavior — show the error text).
87
+ * Shared vocabulary with the extension (keep both lists identical). */
88
+ export const CHAT_ERROR_REASONS = [
89
+ "bridge-disconnected", // the worker's bridge closed mid-turn
90
+ "bridge-unresponsive", // the socket looked open but the worker never answered
91
+ "no-worker", // no worker is running for this tab
92
+ "session-busy", // a turn is already in flight for this session
93
+ "session-disposed", // the worker session was torn down
94
+ "token-expired", // F5 XC API token expired
95
+ "token-expiring", // F5 XC API token is about to expire
96
+ "provider-4xx", // upstream provider rejected the request (client error)
97
+ "provider-5xx", // upstream provider failed (server error) — retryable
98
+ ] as const;
99
+
100
+ export type ChatErrorReason = (typeof CHAT_ERROR_REASONS)[number];
101
+
83
102
  export interface ChatError {
84
103
  type: "chat_error";
85
104
  id: string;
86
105
  error: string;
106
+ reason?: ChatErrorReason;
87
107
  }
88
108
 
89
109
  // ---------------------------------------------------------------------------
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.61.5",
21
- "commit": "e9f45bc1c87e84c1d99060a016ae1e20907e2210",
22
- "shortCommit": "e9f45bc",
20
+ "version": "19.62.0",
21
+ "commit": "002bc2b7d7e14a95e282f940730cbdc347197635",
22
+ "shortCommit": "002bc2b",
23
23
  "branch": "main",
24
- "tag": "v19.61.5",
25
- "commitDate": "2026-07-08T01:47:55Z",
26
- "buildDate": "2026-07-08T02:09:52.692Z",
24
+ "tag": "v19.62.0",
25
+ "commitDate": "2026-07-08T02:22:10Z",
26
+ "buildDate": "2026-07-08T02:45:44.169Z",
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/e9f45bc1c87e84c1d99060a016ae1e20907e2210",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.61.5"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/002bc2b7d7e14a95e282f940730cbdc347197635",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.62.0"
33
33
  };