@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 +14 -0
- package/package.json +7 -7
- package/src/internal-urls/build-info.generated.ts +9 -9
- package/src/prompts/agents/explore.md +2 -1
- package/src/prompts/system/subagent-submit-reminder.md +3 -7
- package/src/session/messages.ts +120 -2
- package/src/task/executor.ts +26 -1
- package/src/utils/tool-choice.ts +1 -1
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.
|
|
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.
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "18.53.
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "18.53.
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "18.53.
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "18.53.
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "18.53.
|
|
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.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.53.1",
|
|
21
|
+
"commit": "8c2358f28cbf35bd5a8ce14498666912f6ffb0da",
|
|
22
|
+
"shortCommit": "8c2358f",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.53.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
27
|
-
"dirty":
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.53.
|
|
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/
|
|
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
|
|
5
|
-
-
|
|
6
|
-
-
|
|
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>
|
package/src/session/messages.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/task/executor.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/src/utils/tool-choice.ts
CHANGED