@f5-sales-demo/xcsh 22.7.1 → 22.7.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.
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "22.7.1",
21
- "commit": "37e866e5b93ca06479006dc54620a6b4c21347f5",
22
- "shortCommit": "37e866e",
20
+ "version": "22.7.2",
21
+ "commit": "fb6e63af2545331d0ae899f6faae6588af086b92",
22
+ "shortCommit": "fb6e63a",
23
23
  "branch": "main",
24
- "tag": "v22.7.1",
25
- "commitDate": "2026-10-01T15:45:28Z",
26
- "buildDate": "2026-10-01T16:30:06.683Z",
24
+ "tag": "v22.7.2",
25
+ "commitDate": "2026-10-01T16:08:52-04:00",
26
+ "buildDate": "2026-10-01T20:55:11.342Z",
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/37e866e5b93ca06479006dc54620a6b4c21347f5",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.7.1"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/fb6e63af2545331d0ae899f6faae6588af086b92",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.7.2"
33
33
  };
@@ -802,6 +802,10 @@ export class AgentSession {
802
802
  this.#syncRoutingStateFromBranch();
803
803
  this.#ttsrManager = config.ttsrManager;
804
804
  this.#obfuscator = config.obfuscator;
805
+ this.agent.setAssistantCheckpointHandler(async message => {
806
+ this.sessionManager.appendAssistantCheckpoint(message);
807
+ await this.sessionManager.flush();
808
+ });
805
809
  this.agent.setAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
806
810
  const event: AgentEvent = {
807
811
  type: "message_update",
@@ -1363,7 +1367,13 @@ export class AgentSession {
1363
1367
  // run during tool execution, which happens between message_end and turn_end.
1364
1368
  if (event.type === "turn_end" && this.#toolChoiceQueue.hasInFlight) {
1365
1369
  const msg = event.message as AssistantMessage;
1366
- if (msg.stopReason === "aborted" || msg.stopReason === "error") {
1370
+ if (
1371
+ (msg.stopReason === "aborted" || msg.stopReason === "error") &&
1372
+ !(
1373
+ event.toolChoiceServed ||
1374
+ (msg.interruption && (event.toolResults.length > 0 || msg.interruption.toolChoiceServed))
1375
+ )
1376
+ ) {
1367
1377
  this.#toolChoiceQueue.reject(msg.stopReason === "error" ? "error" : "aborted");
1368
1378
  } else {
1369
1379
  this.#toolChoiceQueue.resolve();
@@ -6723,7 +6733,7 @@ export class AgentSession {
6723
6733
  * Usage-limit errors are retryable because the retry handler performs credential switching.
6724
6734
  */
6725
6735
  #isRetryableError(message: AssistantMessage): boolean {
6726
- if (message.stopReason !== "error" || !message.errorMessage) return false;
6736
+ if (message.interruption || message.stopReason !== "error" || !message.errorMessage) return false;
6727
6737
 
6728
6738
  // Context overflow is handled by compaction, not retry
6729
6739
  const contextWindow = this.model ? getModelEffectiveContextWindow(this.model) : 0;
@@ -218,6 +218,8 @@ export function createCompactionSummaryMessage(
218
218
  }
219
219
 
220
220
  export function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: AssistantMessage): AssistantMessage {
221
+ // Codex completed native items support full-history replay after reconnect and reload.
222
+ if (message.api === "openai-codex-responses" && message.interruption) return message;
221
223
  if (message.providerPayload?.type !== "openaiResponsesHistory") {
222
224
  return message;
223
225
  }
@@ -3,6 +3,7 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { AgentMessage } from "@f5-sales-demo/pi-agent-core";
5
5
  import type {
6
+ AssistantMessage,
6
7
  ImageContent,
7
8
  Message,
8
9
  MessageAttribution,
@@ -238,7 +239,13 @@ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
238
239
  }
239
240
 
240
241
  /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
242
+ export interface AssistantCheckpointEntry extends SessionEntryBase {
243
+ type: "assistant_checkpoint";
244
+ message: AssistantMessage;
245
+ }
246
+
241
247
  export type SessionEntry =
248
+ | AssistantCheckpointEntry
242
249
  | SessionMessageEntry
243
250
  | ThinkingLevelChangeEntry
244
251
  | ModelChangeEntry
@@ -670,9 +677,28 @@ export function buildSessionContext(
670
677
  // 3. Emit messages after compaction
671
678
  const messages: AgentMessage[] = [];
672
679
 
680
+ const checkpointTimestamps = new Set(
681
+ path.filter(e => e.type === "assistant_checkpoint").map(e => (e as AssistantCheckpointEntry).message.timestamp),
682
+ );
673
683
  const appendMessage = (entry: SessionEntry) => {
674
- if (entry.type === "message") {
675
- messages.push(entry.message);
684
+ if (entry.type === "message" || entry.type === "assistant_checkpoint") {
685
+ const message = entry.message;
686
+ if (message.role === "assistant") {
687
+ const retained = message.interruption
688
+ ? {
689
+ ...message,
690
+ content: message.content.filter((_c, i) =>
691
+ message.interruption!.completedContentIndices.includes(i),
692
+ ),
693
+ }
694
+ : message;
695
+ const previous =
696
+ checkpointTimestamps.has(message.timestamp) || message.interruption
697
+ ? messages.findIndex(m => m.role === "assistant" && m.timestamp === message.timestamp)
698
+ : -1;
699
+ if (previous >= 0) messages[previous] = retained;
700
+ else messages.push(retained);
701
+ } else messages.push(message);
676
702
  } else if (entry.type === "custom_message") {
677
703
  messages.push(
678
704
  createCustomMessage(
@@ -1859,7 +1885,9 @@ export class SessionManager {
1859
1885
  // hadSessionFile: file existed before move → must rewrite to update cwd
1860
1886
  // hasAssistant: assistant messages in memory but file missing → recreate from memory
1861
1887
  // Neither true → fresh session, never written → preserve lazy-persist
1862
- const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
1888
+ const hasAssistant = this.#fileEntries.some(
1889
+ e => (e.type === "message" || e.type === "assistant_checkpoint") && e.message.role === "assistant",
1890
+ );
1863
1891
  if (this.persist && this.#sessionFile && (hadSessionFile || hasAssistant)) {
1864
1892
  await this.#rewriteFile();
1865
1893
  }
@@ -2355,7 +2383,9 @@ export class SessionManager {
2355
2383
  // creating files for sessions that never produce output. Once ensureOnDisk() has
2356
2384
  // been called, the session is already on disk and every entry must be flushed.
2357
2385
  if (!this.#ensuredOnDisk) {
2358
- const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
2386
+ const hasAssistant = this.#fileEntries.some(
2387
+ e => (e.type === "message" || e.type === "assistant_checkpoint") && e.message.role === "assistant",
2388
+ );
2359
2389
  if (!hasAssistant) {
2360
2390
  // Mark as not flushed so when assistant arrives, all entries get written.
2361
2391
  this.#flushed = false;
@@ -2411,6 +2441,18 @@ export class SessionManager {
2411
2441
  * so it is easier to find them.
2412
2442
  * These need to be appended via appendCompaction() and appendBranchSummary() methods.
2413
2443
  */
2444
+ appendAssistantCheckpoint(message: AssistantMessage): string {
2445
+ const entry: AssistantCheckpointEntry = {
2446
+ type: "assistant_checkpoint",
2447
+ id: generateId(this.#byId),
2448
+ parentId: this.#leafId,
2449
+ timestamp: new Date().toISOString(),
2450
+ message: structuredClone(message),
2451
+ };
2452
+ this.#appendEntry(entry);
2453
+ return entry.id;
2454
+ }
2455
+
2414
2456
  appendMessage(
2415
2457
  message:
2416
2458
  | Message