@f5xc-salesdemos/xcsh 18.63.0 → 18.63.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.63.0",
4
+ "version": "18.63.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.63.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.63.0",
53
- "@f5xc-salesdemos/pi-ai": "18.63.0",
54
- "@f5xc-salesdemos/pi-natives": "18.63.0",
55
- "@f5xc-salesdemos/pi-tui": "18.63.0",
56
- "@f5xc-salesdemos/pi-utils": "18.63.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.63.1",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.63.1",
53
+ "@f5xc-salesdemos/pi-ai": "18.63.1",
54
+ "@f5xc-salesdemos/pi-natives": "18.63.1",
55
+ "@f5xc-salesdemos/pi-tui": "18.63.1",
56
+ "@f5xc-salesdemos/pi-utils": "18.63.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.63.0",
21
- "commit": "8d5a9fc0bb6d4a264e25e3066f29273b7f0a6760",
22
- "shortCommit": "8d5a9fc",
20
+ "version": "18.63.1",
21
+ "commit": "cc43e44c0c304f8ed8778d3dc1566286cd656275",
22
+ "shortCommit": "cc43e44",
23
23
  "branch": "main",
24
- "tag": "v18.63.0",
25
- "commitDate": "2026-05-11T07:20:24Z",
26
- "buildDate": "2026-05-11T07:45:28.251Z",
27
- "dirty": true,
24
+ "tag": "v18.63.1",
25
+ "commitDate": "2026-05-13T17:59:16Z",
26
+ "buildDate": "2026-05-13T18:24:01.435Z",
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/8d5a9fc0bb6d4a264e25e3066f29273b7f0a6760",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.63.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/cc43e44c0c304f8ed8778d3dc1566286cd656275",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.63.1"
33
33
  };
@@ -374,6 +374,12 @@ function repairToolResultOrdering(messages: Message[]): Message[] {
374
374
  // When an assistant-with-tool-calls gets displaced (wedged between another assistant's
375
375
  // tool_use and its tool_result), the first pass pushes it to result but the outer loop
376
376
  // jumps past it — so its own tool_results are never resolved.
377
+ //
378
+ // Uses a non-mutating rebuild to avoid index corruption from splice-during-iteration
379
+ // (the prior splice approach corrupted indices in long conversations with 1000+ messages).
380
+ const indicesToRemove = new Set<number>();
381
+ const insertions: Array<{ afterIndex: number; messages: ToolResultMessage[] }> = [];
382
+
377
383
  for (let i = 0; i < result.length; i++) {
378
384
  const msg = result[i];
379
385
  if (msg.role !== "assistant") continue;
@@ -381,7 +387,6 @@ function repairToolResultOrdering(messages: Message[]): Message[] {
381
387
  const toolCalls = assistantMsg.content.filter((c): c is ToolCall => c.type === "toolCall");
382
388
  if (toolCalls.length === 0) continue;
383
389
 
384
- // Check if every tool call has a toolResult immediately following
385
390
  const expectedIds = new Set(toolCalls.map(tc => tc.id));
386
391
  let j = i + 1;
387
392
  while (j < result.length && result[j].role === "toolResult") {
@@ -391,17 +396,18 @@ function repairToolResultOrdering(messages: Message[]): Message[] {
391
396
 
392
397
  if (expectedIds.size === 0) continue;
393
398
 
394
- // For missing tool calls: relocate existing result from later in array, or synthesize
395
399
  const toInsert: ToolResultMessage[] = [];
396
400
  for (const id of expectedIds) {
397
- // Check if a toolResult for this ID exists later in result
398
401
  const laterIndex = result.findIndex(
399
- (m, idx) => idx > j && m.role === "toolResult" && (m as ToolResultMessage).toolCallId === id,
402
+ (m, idx) =>
403
+ idx > j &&
404
+ !indicesToRemove.has(idx) &&
405
+ m.role === "toolResult" &&
406
+ (m as ToolResultMessage).toolCallId === id,
400
407
  );
401
408
  if (laterIndex !== -1) {
402
- // Relocate the existing result to right after the assistant
403
- const [relocated] = result.splice(laterIndex, 1);
404
- toInsert.push(relocated as ToolResultMessage);
409
+ toInsert.push(result[laterIndex] as ToolResultMessage);
410
+ indicesToRemove.add(laterIndex);
405
411
  } else {
406
412
  const toolCall = toolCalls.find(tc => tc.id === id);
407
413
  toInsert.push({
@@ -414,9 +420,26 @@ function repairToolResultOrdering(messages: Message[]): Message[] {
414
420
  } as ToolResultMessage);
415
421
  }
416
422
  }
417
- result.splice(i + 1, 0, ...toInsert);
418
- i += toInsert.length; // Skip past inserted results
419
- repaired = true;
423
+ if (toInsert.length > 0) {
424
+ insertions.push({ afterIndex: i, messages: toInsert });
425
+ repaired = true;
426
+ }
427
+ }
428
+
429
+ if (insertions.length > 0 || indicesToRemove.size > 0) {
430
+ const rebuilt: Message[] = [];
431
+ const insertionMap = new Map(insertions.map(ins => [ins.afterIndex, ins.messages]));
432
+ for (let i = 0; i < result.length; i++) {
433
+ if (!indicesToRemove.has(i)) {
434
+ rebuilt.push(result[i]);
435
+ }
436
+ const ins = insertionMap.get(i);
437
+ if (ins) {
438
+ rebuilt.push(...ins);
439
+ }
440
+ }
441
+ result.length = 0;
442
+ result.push(...rebuilt);
420
443
  }
421
444
 
422
445
  if (repaired) {
@@ -536,5 +559,39 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
536
559
  }
537
560
  })
538
561
  .filter(m => m !== undefined);
539
- return repairToolResultOrdering(converted);
562
+ const repaired = repairToolResultOrdering(converted);
563
+ return mergeConsecutiveUserTextMessages(repaired);
564
+ }
565
+
566
+ /**
567
+ * Merge consecutive user messages that contain only text into single messages.
568
+ * The Claude API internally merges consecutive same-role messages before validation,
569
+ * which shifts message indices. Pre-merging here keeps xcsh's indices aligned with
570
+ * the API's view, preventing phantom index mismatches in error reports.
571
+ */
572
+ function mergeConsecutiveUserTextMessages(messages: Message[]): Message[] {
573
+ if (messages.length < 2) return messages;
574
+
575
+ const result: Message[] = [messages[0]];
576
+ for (let i = 1; i < messages.length; i++) {
577
+ const prev = result[result.length - 1];
578
+ const curr = messages[i];
579
+
580
+ if (
581
+ prev.role === "user" &&
582
+ curr.role === "user" &&
583
+ Array.isArray(prev.content) &&
584
+ Array.isArray(curr.content) &&
585
+ prev.content.every(c => c.type === "text") &&
586
+ curr.content.every(c => c.type === "text")
587
+ ) {
588
+ result[result.length - 1] = {
589
+ ...prev,
590
+ content: [...prev.content, ...curr.content],
591
+ };
592
+ } else {
593
+ result.push(curr);
594
+ }
595
+ }
596
+ return result;
540
597
  }