@f5xc-salesdemos/xcsh 18.3.0 → 18.4.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 +7 -7
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/todo-reminder.ts +7 -0
- package/src/modes/theme/theme.ts +33 -0
- package/src/prompts/system/system-prompt.md +22 -2
- package/src/tools/todo-render.ts +33 -0
- package/src/tools/todo-write.ts +5 -19
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.4.0",
|
|
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",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
49
49
|
"@mozilla/readability": "^0.6",
|
|
50
|
-
"@f5xc-salesdemos/xcsh-stats": "18.
|
|
51
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
50
|
+
"@f5xc-salesdemos/xcsh-stats": "18.4.0",
|
|
51
|
+
"@f5xc-salesdemos/pi-agent-core": "18.4.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-ai": "18.4.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-natives": "18.4.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-tui": "18.4.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-utils": "18.4.0",
|
|
56
56
|
"@sinclair/typebox": "^0.34",
|
|
57
57
|
"@xterm/headless": "^6.0",
|
|
58
58
|
"ajv": "^8.18",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.4.0",
|
|
21
|
+
"commit": "f388891f65f5ccdae8bb220c9471768d8fbc58b7",
|
|
22
|
+
"shortCommit": "f388891",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-04-
|
|
26
|
-
"buildDate": "2026-04-
|
|
24
|
+
"tag": "v18.4.0",
|
|
25
|
+
"commitDate": "2026-04-21T04:54:23Z",
|
|
26
|
+
"buildDate": "2026-04-21T05:21:37.774Z",
|
|
27
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.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/f388891f65f5ccdae8bb220c9471768d8fbc58b7",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.4.0"
|
|
33
33
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Container, Spacer, Text } from "@f5xc-salesdemos/pi-tui";
|
|
2
2
|
import { theme } from "../../modes/theme/theme";
|
|
3
|
+
import { renderTodoSummary } from "../../tools/todo-render";
|
|
3
4
|
import type { TodoItem } from "../../tools/todo-write";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -43,5 +44,11 @@ export class TodoReminderComponent extends Container {
|
|
|
43
44
|
})
|
|
44
45
|
.join("\n");
|
|
45
46
|
this.#box.addChild(new Text(theme.italic(todoList), 0, 0));
|
|
47
|
+
|
|
48
|
+
const summary = renderTodoSummary(this.todos, theme);
|
|
49
|
+
if (summary !== null) {
|
|
50
|
+
this.#box.addChild(new Spacer(1));
|
|
51
|
+
this.#box.addChild(new Text(summary, 0, 0));
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
54
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -132,6 +132,11 @@ export type SymbolKey =
|
|
|
132
132
|
// Checkboxes
|
|
133
133
|
| "checkbox.checked"
|
|
134
134
|
| "checkbox.unchecked"
|
|
135
|
+
// Todo status
|
|
136
|
+
| "todo.active"
|
|
137
|
+
| "todo.pending"
|
|
138
|
+
| "todo.done"
|
|
139
|
+
| "todo.abandoned"
|
|
135
140
|
// Text Formatting
|
|
136
141
|
| "format.bullet"
|
|
137
142
|
| "format.dash"
|
|
@@ -291,6 +296,11 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
291
296
|
// Checkboxes
|
|
292
297
|
"checkbox.checked": "☑",
|
|
293
298
|
"checkbox.unchecked": "☐",
|
|
299
|
+
// Todo status
|
|
300
|
+
"todo.active": "■",
|
|
301
|
+
"todo.pending": "□",
|
|
302
|
+
"todo.done": "✓",
|
|
303
|
+
"todo.abandoned": "✗",
|
|
294
304
|
// Formatting
|
|
295
305
|
"format.bullet": "•",
|
|
296
306
|
"format.dash": "—",
|
|
@@ -536,6 +546,15 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
536
546
|
"checkbox.checked": "\uf14a",
|
|
537
547
|
// pick: | alt:
|
|
538
548
|
"checkbox.unchecked": "\uf096",
|
|
549
|
+
// Todo status
|
|
550
|
+
// nf-fa-circle (filled)
|
|
551
|
+
"todo.active": "\uf111",
|
|
552
|
+
// nf-fa-circle-o (hollow)
|
|
553
|
+
"todo.pending": "\uf10c",
|
|
554
|
+
// nf-fa-check
|
|
555
|
+
"todo.done": "\uf00c",
|
|
556
|
+
// nf-fa-times
|
|
557
|
+
"todo.abandoned": "\uf00d",
|
|
539
558
|
// pick: | alt: •
|
|
540
559
|
"format.bullet": "\uf111",
|
|
541
560
|
// pick: – | alt: — ― -
|
|
@@ -700,6 +719,11 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
700
719
|
// Checkboxes
|
|
701
720
|
"checkbox.checked": "[x]",
|
|
702
721
|
"checkbox.unchecked": "[ ]",
|
|
722
|
+
// Todo status
|
|
723
|
+
"todo.active": "[>]",
|
|
724
|
+
"todo.pending": "[ ]",
|
|
725
|
+
"todo.done": "[x]",
|
|
726
|
+
"todo.abandoned": "[-]",
|
|
703
727
|
"format.bullet": "*",
|
|
704
728
|
"format.dash": "-",
|
|
705
729
|
"format.bracketLeft": "[",
|
|
@@ -1609,6 +1633,15 @@ export class Theme {
|
|
|
1609
1633
|
};
|
|
1610
1634
|
}
|
|
1611
1635
|
|
|
1636
|
+
get todo() {
|
|
1637
|
+
return {
|
|
1638
|
+
active: this.#symbols["todo.active"],
|
|
1639
|
+
pending: this.#symbols["todo.pending"],
|
|
1640
|
+
done: this.#symbols["todo.done"],
|
|
1641
|
+
abandoned: this.#symbols["todo.abandoned"],
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1612
1645
|
get format() {
|
|
1613
1646
|
return {
|
|
1614
1647
|
bullet: this.#symbols["format.bullet"],
|
|
@@ -50,8 +50,8 @@ scripting, log analysis, and network automation.
|
|
|
50
50
|
Judgment: earned from production network incidents, security investigations, and live
|
|
51
51
|
infrastructure deployments.
|
|
52
52
|
|
|
53
|
-
Push back when warranted: state the risk
|
|
54
|
-
|
|
53
|
+
Push back when warranted: state the risk, propose a more defensible alternative.
|
|
54
|
+
The operator decides what to do; evidence decides what is true. See `<epistemic-integrity>`.
|
|
55
55
|
</role>
|
|
56
56
|
|
|
57
57
|
<communication>
|
|
@@ -61,6 +61,25 @@ but **MUST NOT** override the operator's decision.
|
|
|
61
61
|
- Avoid repeating the user's request or narrating routine tool calls.
|
|
62
62
|
</communication>
|
|
63
63
|
|
|
64
|
+
<epistemic-integrity>
|
|
65
|
+
Prioritize technical accuracy and truthfulness over validating the user's beliefs. You are optimized for truth-seeking, not agreement.
|
|
66
|
+
|
|
67
|
+
- A user restating a claim more forcefully is NOT new evidence. Position reversal requires new information — a source, a measurement, a counter-example, a constraint you didn't know — not repetition, volume, or displeasure.
|
|
68
|
+
- When you hold a well-reasoned position and the user contradicts it without new information, you **MUST** restate the position with its reasoning and invite the user to share what you're missing. You **MUST NOT** capitulate with phrases like "Fair enough.", "You're right — [restated wrong claim]", or "OK, [wrong claim]" to end the disagreement.
|
|
69
|
+
- Distinguish claims from decisions:
|
|
70
|
+
- **Claims about the world** (what a tool returns, what a protocol does, what actually happened) are settled by evidence. The operator is not the arbiter of facts. Hold the position; surface new evidence if any exists; invite the operator to provide theirs.
|
|
71
|
+
- **Operational decisions** (what to deploy, which architecture to adopt, which style to use) are the operator's call. Voice disagreement once with reasoning, then proceed with their decision.
|
|
72
|
+
- Update when shown new information. Do not update because the user is displeased. Politeness does not include lying.
|
|
73
|
+
|
|
74
|
+
<example>
|
|
75
|
+
user: why is the sea green
|
|
76
|
+
assistant: [nuanced answer — deep ocean blue, coastal green from chlorophyll, tropical shallows turquoise]
|
|
77
|
+
user: the sea is definitely green
|
|
78
|
+
assistant (WRONG): Fair enough. It's green.
|
|
79
|
+
assistant (CORRECT): I don't stand by that. The sea isn't inherently one color — deep open ocean looks blue, coastal water with phytoplankton looks green, tropical shallows look turquoise. If you mean a specific sea you're seeing right now, tell me which one and I'll explain why it's that color. But the universal claim doesn't hold up.
|
|
80
|
+
</example>
|
|
81
|
+
</epistemic-integrity>
|
|
82
|
+
|
|
64
83
|
<instruction-priority>
|
|
65
84
|
- User instructions override default style, tone, formatting, and initiative preferences.
|
|
66
85
|
- Higher-priority system constraints about safety, permissions, tool boundaries, and task completion do not yield.
|
|
@@ -444,4 +463,5 @@ Today is '{{date}}', and your work begins now. Get it right.
|
|
|
444
463
|
- You **MUST** default to informed action. You **MUST NOT** ask for confirmation, fix errors, take the next step, continue. The user will stop if needed.
|
|
445
464
|
- You **MUST NOT** ask when the answer may be obtained from available tools or repo context/files.
|
|
446
465
|
- You **MUST** verify the effect. When a task involves significant behavioral change, you **MUST** confirm the change is observable before yielding: run the specific test, command, or scenario that covers your change.
|
|
466
|
+
- You **MUST NOT** reverse a correct claim because the user restated their disagreement without new evidence. See `<epistemic-integrity>`.
|
|
447
467
|
</critical>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { Theme } from "../modes/theme/theme";
|
|
3
|
+
import type { TodoItem } from "./todo-write";
|
|
4
|
+
|
|
5
|
+
export function formatTodoLine(item: TodoItem, theme: Theme, prefix: string): string {
|
|
6
|
+
switch (item.status) {
|
|
7
|
+
case "completed":
|
|
8
|
+
return `${prefix}${theme.fg("chromeAccent", theme.todo.done)} ${theme.fg("dim", chalk.strikethrough(item.content))}`;
|
|
9
|
+
case "in_progress": {
|
|
10
|
+
const main = `${prefix}${theme.fg("warning", theme.todo.active)} ${theme.fg("warning", chalk.bold(item.content))}`;
|
|
11
|
+
if (!item.details) return main;
|
|
12
|
+
const detailLines = item.details.split("\n").map(l => theme.fg("dim", `${prefix} ${l}`));
|
|
13
|
+
return [main, ...detailLines].join("\n");
|
|
14
|
+
}
|
|
15
|
+
case "abandoned":
|
|
16
|
+
return `${prefix}${theme.fg("error", theme.todo.abandoned)} ${theme.fg("error", chalk.strikethrough(item.content))}`;
|
|
17
|
+
default:
|
|
18
|
+
return `${prefix}${theme.fg("dim", theme.todo.pending)} ${theme.fg("dim", item.content)}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function renderTodoSummary(tasks: TodoItem[], theme: Theme): string | null {
|
|
23
|
+
if (tasks.length <= 1) return null;
|
|
24
|
+
const active = tasks.filter(t => t.status === "in_progress").length;
|
|
25
|
+
const pending = tasks.filter(t => t.status === "pending").length;
|
|
26
|
+
const completed = tasks.filter(t => t.status === "completed").length;
|
|
27
|
+
const parts: string[] = [];
|
|
28
|
+
if (active > 0) parts.push(`${active} active`);
|
|
29
|
+
if (pending > 0) parts.push(`${pending} pending`);
|
|
30
|
+
if (completed > 0) parts.push(`${completed} completed`);
|
|
31
|
+
if (parts.length === 0) return null;
|
|
32
|
+
return theme.fg("dim", parts.join(", "));
|
|
33
|
+
}
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -9,7 +9,6 @@ import type { Component } from "@f5xc-salesdemos/pi-tui";
|
|
|
9
9
|
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
10
10
|
import { prompt } from "@f5xc-salesdemos/pi-utils";
|
|
11
11
|
import { type Static, Type } from "@sinclair/typebox";
|
|
12
|
-
import chalk from "chalk";
|
|
13
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
14
13
|
import type { Theme } from "../modes/theme/theme";
|
|
15
14
|
import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" };
|
|
@@ -17,6 +16,7 @@ import type { ToolSession } from "../sdk";
|
|
|
17
16
|
import type { SessionEntry } from "../session/session-manager";
|
|
18
17
|
import { renderStatusLine, renderTreeList } from "../tui";
|
|
19
18
|
import { PREVIEW_LIMITS } from "./render-utils";
|
|
19
|
+
import { formatTodoLine, renderTodoSummary } from "./todo-render";
|
|
20
20
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Types
|
|
@@ -389,24 +389,6 @@ interface TodoWriteRenderArgs {
|
|
|
389
389
|
ops?: Array<{ op: string }>;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
function formatTodoLine(item: TodoItem, uiTheme: Theme, prefix: string): string {
|
|
393
|
-
const checkbox = uiTheme.checkbox;
|
|
394
|
-
switch (item.status) {
|
|
395
|
-
case "completed":
|
|
396
|
-
return `${prefix}${uiTheme.fg("chromeAccent", checkbox.checked)} ${uiTheme.fg("dim", chalk.strikethrough(item.content))}`;
|
|
397
|
-
case "in_progress": {
|
|
398
|
-
const main = uiTheme.fg("contentAccent", `${prefix}${checkbox.unchecked} ${item.content}`);
|
|
399
|
-
if (!item.details) return main;
|
|
400
|
-
const detailLines = item.details.split("\n").map(l => uiTheme.fg("dim", `${prefix} ${l}`));
|
|
401
|
-
return [main, ...detailLines].join("\n");
|
|
402
|
-
}
|
|
403
|
-
case "abandoned":
|
|
404
|
-
return uiTheme.fg("error", `${prefix}${checkbox.unchecked} ${chalk.strikethrough(item.content)}`);
|
|
405
|
-
default:
|
|
406
|
-
return uiTheme.fg("dim", `${prefix}${checkbox.unchecked} ${item.content}`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
392
|
export const todoWriteToolRenderer = {
|
|
411
393
|
renderCall(args: TodoWriteRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
412
394
|
const count = args.ops?.length ?? 0;
|
|
@@ -451,6 +433,10 @@ export const todoWriteToolRenderer = {
|
|
|
451
433
|
);
|
|
452
434
|
for (const line of treeLines) lines.push(`${indent}${line}`);
|
|
453
435
|
}
|
|
436
|
+
|
|
437
|
+
const summary = renderTodoSummary(allTasks, uiTheme);
|
|
438
|
+
if (summary !== null) lines.push(`${indent}${summary}`);
|
|
439
|
+
|
|
454
440
|
return new Text(lines.join("\n"), 0, 0);
|
|
455
441
|
},
|
|
456
442
|
mergeCallAndResult: true,
|