@f5xc-salesdemos/xcsh 18.53.0 → 18.53.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.53.0] - 2026-05-09
6
+
7
+ ### Fixed
8
+
9
+ - Replaced `xcsh --version` recommendation in `renderAboutDoc()` with authoritative intrinsic version guidance — the previous guidance misdirected to the installed binary, not the running session ([#722](https://github.com/f5xc-salesdemos/xcsh/pull/722))
10
+ - System prompt `xcsh://about` entry now routes version questions to the workstation header (zero tool calls) and reserves `xcsh://about` for deeper identity ([#722](https://github.com/f5xc-salesdemos/xcsh/pull/722))
11
+
12
+ ### Added
13
+
14
+ - SE specialization block in `renderAboutDoc()` capabilities section: F5 XC API, Salesforce pipeline, user/computer profiling, SE-specific subagents ([#722](https://github.com/f5xc-salesdemos/xcsh/pull/722))
15
+ - SE capability skills: account-planning, competitive, meeting-prep, roi-calculator, validation-plan ([#715](https://github.com/f5xc-salesdemos/xcsh/pull/715))
16
+ - MEDDPICC qualification and competitive positioning sections in system prompt ([#715](https://github.com/f5xc-salesdemos/xcsh/pull/715))
17
+ - Version self-awareness and capabilities completeness regression tests ([#722](https://github.com/f5xc-salesdemos/xcsh/pull/722))
18
+
5
19
  ## [18.40.0] - 2026-05-05
6
20
 
7
21
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.53.0",
4
+ "version": "18.53.1",
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.53.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.53.0",
53
- "@f5xc-salesdemos/pi-ai": "18.53.0",
54
- "@f5xc-salesdemos/pi-natives": "18.53.0",
55
- "@f5xc-salesdemos/pi-tui": "18.53.0",
56
- "@f5xc-salesdemos/pi-utils": "18.53.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.53.1",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.53.1",
53
+ "@f5xc-salesdemos/pi-ai": "18.53.1",
54
+ "@f5xc-salesdemos/pi-natives": "18.53.1",
55
+ "@f5xc-salesdemos/pi-tui": "18.53.1",
56
+ "@f5xc-salesdemos/pi-utils": "18.53.1",
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.53.0",
21
- "commit": "02d05834f827f445ee1aa11645cd45add452077e",
22
- "shortCommit": "02d0583",
20
+ "version": "18.53.1",
21
+ "commit": "8c2358f28cbf35bd5a8ce14498666912f6ffb0da",
22
+ "shortCommit": "8c2358f",
23
23
  "branch": "main",
24
- "tag": "v18.53.0",
25
- "commitDate": "2026-05-09T08:18:32Z",
26
- "buildDate": "2026-05-09T08:40:37.845Z",
27
- "dirty": true,
24
+ "tag": "v18.53.1",
25
+ "commitDate": "2026-05-09T09:32:57Z",
26
+ "buildDate": "2026-05-09T09:56:11.380Z",
27
+ "dirty": false,
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/02d05834f827f445ee1aa11645cd45add452077e",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.53.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/8c2358f28cbf35bd5a8ce14498666912f6ffb0da",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.53.1"
33
33
  };
@@ -2,7 +2,7 @@
2
2
  name: explore
3
3
  description: Fast read-only codebase scout returning compressed context for handoff
4
4
  tools: read, grep, find, web_search
5
- model: pi/smol
5
+ model: pi/task
6
6
  thinking-level: med
7
7
  output:
8
8
  properties:
@@ -10,6 +10,7 @@ output:
10
10
  metadata:
11
11
  description: Brief summary of findings and conclusions
12
12
  type: string
13
+ optionalProperties:
13
14
  files:
14
15
  metadata:
15
16
  description: Files examined with relevant code references
@@ -1,11 +1,7 @@
1
1
  <system-reminder>
2
2
  You stopped without calling submit_result. This is reminder {{retryCount}} of {{maxRetries}}.
3
3
 
4
- You **MUST** call submit_result as your only action now. Choose one:
5
- - If task is complete: call submit_result with your result in `result.data`
6
- - If task failed: call submit_result with `result.error` describing what happened
7
-
8
- You **MUST NOT** give up if you can still complete the task through exploration (using available tools or repo context). If you submit an error, you **MUST** include what you tried and the exact blocker.
9
-
10
- You **MUST NOT** output text without a tool call. You **MUST** call submit_result to finish.
4
+ You **MUST** call submit_result now. No other tool calls, no text output.
5
+ - Task done: `submit_result` with `result.data` containing your findings
6
+ - Task blocked: `submit_result` with `result.error` describing the blocker
11
7
  </system-reminder>
@@ -12,9 +12,10 @@ import type {
12
12
  MessageAttribution,
13
13
  ProviderPayload,
14
14
  TextContent,
15
+ ToolCall,
15
16
  ToolResultMessage,
16
17
  } from "@f5xc-salesdemos/pi-ai";
17
- import { prompt } from "@f5xc-salesdemos/pi-utils";
18
+ import { logger, prompt } from "@f5xc-salesdemos/pi-utils";
18
19
  import branchSummaryContextPrompt from "../prompts/compaction/branch-summary-context.md" with { type: "text" };
19
20
  import compactionSummaryContextPrompt from "../prompts/compaction/compaction-summary-context.md" with { type: "text" };
20
21
  import type { OutputMeta } from "../tools/output-meta";
@@ -260,6 +261,122 @@ export function createCustomMessage(
260
261
  };
261
262
  }
262
263
 
264
+ /**
265
+ * Repair tool_use / tool_result ordering in converted LLM messages.
266
+ *
267
+ * The Claude API requires every assistant message containing tool_use blocks
268
+ * to be immediately followed by the matching tool_result messages. Session
269
+ * corruption (injected messages, compaction boundaries, crash during tool
270
+ * execution) can break this invariant, producing a 400 error that bricks
271
+ * the session.
272
+ *
273
+ * This function:
274
+ * 1. Finds assistant messages with tool_use (toolCall) content
275
+ * 2. Collects the required tool_result IDs
276
+ * 3. If tool_results are elsewhere in the array, moves them to the correct position
277
+ * 4. If tool_results are missing entirely, injects synthetic error tool_results
278
+ * 5. Non-tool messages that got wedged between tool_use and tool_result are relocated
279
+ * to just before the assistant message
280
+ */
281
+ function repairToolResultOrdering(messages: Message[]): Message[] {
282
+ const result: Message[] = [];
283
+ let repaired = false;
284
+
285
+ // Index all toolResult messages by their toolCallId for O(1) lookup
286
+ const toolResultsByCallId = new Map<string, { message: Message; originalIndex: number }>();
287
+ for (let i = 0; i < messages.length; i++) {
288
+ const msg = messages[i];
289
+ if (msg.role === "toolResult") {
290
+ const trMsg = msg as ToolResultMessage;
291
+ toolResultsByCallId.set(trMsg.toolCallId, { message: msg, originalIndex: i });
292
+ }
293
+ }
294
+
295
+ // Track which toolResult messages have been placed by repair
296
+ const placedToolResultIndices = new Set<number>();
297
+
298
+ for (let i = 0; i < messages.length; i++) {
299
+ const msg = messages[i];
300
+
301
+ // Skip toolResult messages that were already placed by repair
302
+ if (msg.role === "toolResult" && placedToolResultIndices.has(i)) {
303
+ continue;
304
+ }
305
+
306
+ result.push(msg);
307
+
308
+ // Not an assistant message with tool calls — nothing to repair
309
+ if (msg.role !== "assistant") continue;
310
+ const assistantMsg = msg as AssistantMessage;
311
+ const toolCalls = assistantMsg.content.filter((c): c is ToolCall => c.type === "toolCall");
312
+ if (toolCalls.length === 0) continue;
313
+
314
+ // Collect required tool call IDs
315
+ const requiredIds = new Set(toolCalls.map(tc => tc.id));
316
+
317
+ // Check what immediately follows in the remaining messages
318
+ // Consume consecutive toolResult messages that match, and relocate any
319
+ // non-toolResult messages that got wedged between
320
+ const displaced: Message[] = [];
321
+ let j = i + 1;
322
+ while (j < messages.length && requiredIds.size > 0) {
323
+ const next = messages[j];
324
+ if (next.role === "toolResult") {
325
+ const trMsg = next as ToolResultMessage;
326
+ if (requiredIds.has(trMsg.toolCallId)) {
327
+ // This tool_result belongs here — place it
328
+ result.push(next);
329
+ placedToolResultIndices.add(j);
330
+ requiredIds.delete(trMsg.toolCallId);
331
+ if (displaced.length > 0) repaired = true;
332
+ j++;
333
+ continue;
334
+ }
335
+ }
336
+ // Non-matching message between tool_use and tool_result — displace it
337
+ displaced.push(next);
338
+ placedToolResultIndices.add(j); // Mark original index as consumed
339
+ j++;
340
+ }
341
+
342
+ // Advance main iterator past consumed messages
343
+ i = j - 1;
344
+
345
+ // Any remaining required IDs: find them later in the array or synthesize
346
+ for (const id of requiredIds) {
347
+ const found = toolResultsByCallId.get(id);
348
+ if (found && !placedToolResultIndices.has(found.originalIndex)) {
349
+ result.push(found.message);
350
+ placedToolResultIndices.add(found.originalIndex);
351
+ repaired = true;
352
+ } else {
353
+ // Missing tool_result entirely — inject synthetic error result
354
+ const toolCall = toolCalls.find(tc => tc.id === id);
355
+ result.push({
356
+ role: "toolResult",
357
+ toolCallId: id,
358
+ toolName: toolCall?.name ?? "unknown",
359
+ content: [{ type: "text", text: "Tool execution was interrupted (session recovery)." }],
360
+ isError: true,
361
+ timestamp: Date.now(),
362
+ } as ToolResultMessage);
363
+ repaired = true;
364
+ }
365
+ }
366
+
367
+ // Re-insert displaced messages after the tool_results
368
+ for (const d of displaced) {
369
+ result.push(d);
370
+ }
371
+ }
372
+
373
+ if (repaired) {
374
+ logger.warn("Repaired tool_use/tool_result ordering in conversation history");
375
+ }
376
+
377
+ return repaired ? result : messages;
378
+ }
379
+
263
380
  /**
264
381
  * Transform AgentMessages (including custom types) to LLM-compatible Messages.
265
382
  *
@@ -269,7 +386,7 @@ export function createCustomMessage(
269
386
  * - Custom extensions and tools
270
387
  */
271
388
  export function convertToLlm(messages: AgentMessage[]): Message[] {
272
- return messages
389
+ const converted = messages
273
390
  .map((m): Message | undefined => {
274
391
  switch (m.role) {
275
392
  case "bashExecution":
@@ -370,4 +487,5 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
370
487
  }
371
488
  })
372
489
  .filter(m => m !== undefined);
490
+ return repairToolResultOrdering(converted);
373
491
  }
@@ -325,6 +325,29 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
325
325
  ? `${SUBAGENT_WARNING_MISSING_SUBMIT_RESULT}\n\n${rawOutput}`
326
326
  : SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
327
327
  }
328
+
329
+ // Salvage output from aborted runs that produced content without calling submit_result
330
+ if (exitCode !== 0 && doneAborted && !signalAborted && rawOutput.trim().length > 0) {
331
+ if (hasOutputSchema) {
332
+ // Try schema-validated fallback: if the model produced valid JSON matching the schema,
333
+ // use it even though submit_result was never called
334
+ const abortFallback = resolveFallbackCompletion(rawOutput, outputSchema);
335
+ if (abortFallback) {
336
+ const completeData = normalizeCompleteData(abortFallback.data, reportFindings);
337
+ try {
338
+ rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
339
+ } catch {
340
+ // Keep rawOutput as-is if serialization fails
341
+ }
342
+ exitCode = 0;
343
+ stderr = "";
344
+ }
345
+ } else {
346
+ // No schema required — raw text output is directly useful
347
+ exitCode = 0;
348
+ stderr = "";
349
+ }
350
+ }
328
351
  }
329
352
 
330
353
  return { rawOutput, exitCode, stderr, abortedViaSubmitResult, hasSubmitResult };
@@ -1250,7 +1273,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1250
1273
  }
1251
1274
 
1252
1275
  // Update final progress
1253
- const wasAborted = abortedViaSubmitResult || (!hasSubmitResult && (done.aborted || signal?.aborted || false));
1276
+ // When salvage recovered the output (exitCode became 0), the result is not aborted.
1277
+ const wasAborted =
1278
+ abortedViaSubmitResult || (!hasSubmitResult && exitCode !== 0 && (done.aborted || signal?.aborted || false));
1254
1279
  const finalAbortReason = wasAborted
1255
1280
  ? abortedViaSubmitResult
1256
1281
  ? submitResultAbortReason
@@ -24,5 +24,5 @@ export function buildNamedToolChoice(toolName: string, model?: Model<Api>): Tool
24
24
  return "required";
25
25
  }
26
26
 
27
- return undefined;
27
+ return "required";
28
28
  }