agent-sh 0.10.2 → 0.10.3
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/dist/agent/agent-loop.js +2 -1
- package/dist/agent/history-file.d.ts +1 -0
- package/dist/agent/history-file.js +12 -5
- package/dist/settings.d.ts +7 -0
- package/dist/settings.js +1 -0
- package/dist/utils/ansi.d.ts +18 -7
- package/dist/utils/ansi.js +62 -141
- package/dist/utils/markdown.js +19 -7
- package/package.json +7 -1
package/dist/agent/agent-loop.js
CHANGED
|
@@ -97,7 +97,8 @@ export class AgentLoop {
|
|
|
97
97
|
// Shell-history-shaped log. Default writes go through the advisable
|
|
98
98
|
// `history:append` handler registered below; extensions swap the
|
|
99
99
|
// backend without touching this wiring.
|
|
100
|
-
|
|
100
|
+
const filePath = process.env.AGENT_SH_HISTORY_FILE || getSettings().historyFilePath;
|
|
101
|
+
this.historyFile = new HistoryFile({ instanceId: this.instanceId, filePath });
|
|
101
102
|
this.conversation = new ConversationState(this.handlers, this.instanceId);
|
|
102
103
|
// Fall back to a single-mode placeholder if the caller passed an
|
|
103
104
|
// empty array (agent-backend does this pre-resolution).
|
|
@@ -12,14 +12,21 @@ import * as crypto from "node:crypto";
|
|
|
12
12
|
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
13
13
|
import { serializeEntry, deserializeEntry, formatNuclearLine, isReadOnly, } from "./nuclear-form.js";
|
|
14
14
|
const HISTORY_PATH = path.join(CONFIG_DIR, "history");
|
|
15
|
-
const LOCK_PATH = HISTORY_PATH + ".lock";
|
|
16
15
|
const LOCK_STALE_MS = 10_000; // consider lock stale after 10s
|
|
17
16
|
export class HistoryFile {
|
|
18
17
|
instanceId;
|
|
19
18
|
filePath;
|
|
19
|
+
lockPath;
|
|
20
20
|
constructor(opts) {
|
|
21
21
|
this.filePath = opts?.filePath ?? HISTORY_PATH;
|
|
22
|
+
this.lockPath = this.filePath + ".lock";
|
|
22
23
|
this.instanceId = opts?.instanceId ?? crypto.randomBytes(2).toString("hex");
|
|
24
|
+
// Custom paths may target a dir that doesn't exist yet; create sync so
|
|
25
|
+
// the first append() can't race with the mkdir.
|
|
26
|
+
try {
|
|
27
|
+
fss.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
catch { /* ignore */ }
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* Append entries atomically. Uses O_APPEND for concurrency safety.
|
|
@@ -218,16 +225,16 @@ export class HistoryFile {
|
|
|
218
225
|
try {
|
|
219
226
|
// Check for stale lock
|
|
220
227
|
try {
|
|
221
|
-
const stat = await fs.stat(
|
|
228
|
+
const stat = await fs.stat(this.lockPath);
|
|
222
229
|
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
223
|
-
await fs.unlink(
|
|
230
|
+
await fs.unlink(this.lockPath).catch(() => { });
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
catch {
|
|
227
234
|
// Lock doesn't exist — good
|
|
228
235
|
}
|
|
229
236
|
// O_EXCL ensures atomicity
|
|
230
|
-
const fd = await fs.open(
|
|
237
|
+
const fd = await fs.open(this.lockPath, fss.constants.O_CREAT | fss.constants.O_EXCL | fss.constants.O_WRONLY);
|
|
231
238
|
await fd.close();
|
|
232
239
|
return true;
|
|
233
240
|
}
|
|
@@ -236,6 +243,6 @@ export class HistoryFile {
|
|
|
236
243
|
}
|
|
237
244
|
}
|
|
238
245
|
async releaseLock() {
|
|
239
|
-
await fs.unlink(
|
|
246
|
+
await fs.unlink(this.lockPath).catch(() => { });
|
|
240
247
|
}
|
|
241
248
|
}
|
package/dist/settings.d.ts
CHANGED
|
@@ -42,6 +42,13 @@ export interface Settings {
|
|
|
42
42
|
historyMaxBytes?: number;
|
|
43
43
|
/** Number of prior history entries to load on startup (default: 50). */
|
|
44
44
|
historyStartupEntries?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Override the history file path. Defaults to `~/.agent-sh/history`.
|
|
47
|
+
* The `AGENT_SH_HISTORY_FILE` env var takes precedence over this setting.
|
|
48
|
+
* Use a per-project path to keep sessions isolated (e.g. embedding apps
|
|
49
|
+
* that boot agent-sh as a library against a specific working tree).
|
|
50
|
+
*/
|
|
51
|
+
historyFilePath?: string;
|
|
45
52
|
/** Auto-compact threshold as fraction of conversation budget (0-1, default 0.5). */
|
|
46
53
|
autoCompactThreshold?: number;
|
|
47
54
|
/** Max command output lines shown inline in TUI. */
|
package/dist/settings.js
CHANGED
|
@@ -21,6 +21,7 @@ const DEFAULTS = {
|
|
|
21
21
|
shellTailLines: 10,
|
|
22
22
|
historyMaxBytes: 104857600, // 100MB — history is only accessed via search/expand, never loaded wholesale
|
|
23
23
|
historyStartupEntries: 100,
|
|
24
|
+
historyFilePath: undefined,
|
|
24
25
|
autoCompactThreshold: 0.5,
|
|
25
26
|
maxCommandOutputLines: 3,
|
|
26
27
|
readOutputMaxLines: 10,
|
package/dist/utils/ansi.d.ts
CHANGED
|
@@ -7,20 +7,29 @@ export declare const GRAY = "\u001B[90m";
|
|
|
7
7
|
export declare const BOLD = "\u001B[1m";
|
|
8
8
|
export declare const RESET = "\u001B[0m";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Returns 2 for wide chars, 1 for normal chars, 0 for combining chars.
|
|
10
|
+
* Width of a single Unicode code point in terminal columns.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
12
|
+
* For correct rendering of emoji clusters (ZWJ, flags, skin-tone, VS16)
|
|
13
|
+
* prefer `clusterWidth` or `visibleLen`, which segment graphemes first.
|
|
14
|
+
* This code-point-level primitive is kept for callers that iterate over
|
|
15
|
+
* chars for wrap-detection purposes (e.g. CJK line-break rules).
|
|
14
16
|
*/
|
|
15
17
|
export declare function charWidth(codePoint: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Width of one grapheme cluster in terminal columns. Handles ZWJ sequences,
|
|
20
|
+
* regional-indicator flags, skin-tone modifiers, and VS16 emoji presentation.
|
|
21
|
+
*/
|
|
22
|
+
export declare function clusterWidth(cluster: string): number;
|
|
16
23
|
/**
|
|
17
24
|
* Measure visible string length in terminal columns.
|
|
18
|
-
* Excludes SGR (color/style) sequences and
|
|
25
|
+
* Excludes SGR (color/style) sequences, and counts each grapheme cluster
|
|
26
|
+
* (emoji, CJK, combining marks) as one terminal-visible unit.
|
|
19
27
|
*/
|
|
20
28
|
export declare function visibleLen(str: string): number;
|
|
21
29
|
/**
|
|
22
30
|
* Truncate a string to fit within `maxWidth` visible columns.
|
|
23
|
-
*
|
|
31
|
+
* Iterates by grapheme cluster so emoji sequences (ZWJ, flags, VS16) are
|
|
32
|
+
* kept intact rather than split mid-cluster. Appends `…` if truncated.
|
|
24
33
|
*/
|
|
25
34
|
export declare function truncateToWidth(str: string, maxWidth: number): string;
|
|
26
35
|
/** Truncate to visible width while preserving SGR sequences — use when
|
|
@@ -28,8 +37,10 @@ export declare function truncateToWidth(str: string, maxWidth: number): string;
|
|
|
28
37
|
export declare function truncateAnsiToWidth(str: string, maxWidth: number): string;
|
|
29
38
|
/**
|
|
30
39
|
* Pad a string with spaces to fill `targetWidth` visible columns.
|
|
31
|
-
* Accounts for CJK double-width characters.
|
|
32
40
|
*/
|
|
33
41
|
export declare function padEndToWidth(str: string, targetWidth: number): string;
|
|
34
|
-
/** Strip
|
|
42
|
+
/** Strip ANSI escape sequences and carriage returns.
|
|
43
|
+
* Delegates escape handling to the `strip-ansi` package (covers SGR, OSC,
|
|
44
|
+
* CSI, private-mode, 8-bit CSI, and newer variants). `\r` is not an escape
|
|
45
|
+
* but callers rely on it being stripped alongside. */
|
|
35
46
|
export declare function stripAnsi(str: string): string;
|
package/dist/utils/ansi.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import stringWidth from "string-width";
|
|
2
|
+
import stripAnsiPkg from "strip-ansi";
|
|
1
3
|
// ── ANSI escape code constants ────────────────────────────────
|
|
2
4
|
export const CYAN = "\x1b[36m";
|
|
3
5
|
export const DIM = "\x1b[2m";
|
|
@@ -8,160 +10,65 @@ export const GRAY = "\x1b[90m";
|
|
|
8
10
|
export const BOLD = "\x1b[1m";
|
|
9
11
|
export const RESET = "\x1b[0m";
|
|
10
12
|
// ── ANSI utility functions ───────────────────────────────────
|
|
13
|
+
// Reused across iterations. Segmenter construction is not free, and the API
|
|
14
|
+
// is pure (no per-call state) so a module-level instance is safe.
|
|
15
|
+
const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
11
16
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Returns 2 for wide chars, 1 for normal chars, 0 for combining chars.
|
|
17
|
+
* Width of a single Unicode code point in terminal columns.
|
|
14
18
|
*
|
|
15
|
-
*
|
|
19
|
+
* For correct rendering of emoji clusters (ZWJ, flags, skin-tone, VS16)
|
|
20
|
+
* prefer `clusterWidth` or `visibleLen`, which segment graphemes first.
|
|
21
|
+
* This code-point-level primitive is kept for callers that iterate over
|
|
22
|
+
* chars for wrap-detection purposes (e.g. CJK line-break rules).
|
|
16
23
|
*/
|
|
17
24
|
export function charWidth(codePoint) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return 0; // Variation Selectors
|
|
31
|
-
if (codePoint >= 0xe0100 && codePoint <= 0xe01ef)
|
|
32
|
-
return 0; // Variation Selectors Supplement
|
|
33
|
-
// Emoji and symbols that render as wide (2 columns)
|
|
34
|
-
// Emoji presentation sequences and keycap
|
|
35
|
-
if (codePoint === 0x20e3)
|
|
36
|
-
return 2; // Combining Enclosing Keycap
|
|
37
|
-
// Emoji blocks
|
|
38
|
-
if (codePoint >= 0x1f600 && codePoint <= 0x1f64f)
|
|
39
|
-
return 2; // Emoticons
|
|
40
|
-
if (codePoint >= 0x1f300 && codePoint <= 0x1f5ff)
|
|
41
|
-
return 2; // Misc Symbols and Pictographs
|
|
42
|
-
if (codePoint >= 0x1f680 && codePoint <= 0x1f6ff)
|
|
43
|
-
return 2; // Transport and Map
|
|
44
|
-
if (codePoint >= 0x1f700 && codePoint <= 0x1f77f)
|
|
45
|
-
return 2; // Alchemical Symbols
|
|
46
|
-
if (codePoint >= 0x1f780 && codePoint <= 0x1f7ff)
|
|
47
|
-
return 2; // Geometric Shapes Extended
|
|
48
|
-
if (codePoint >= 0x1f800 && codePoint <= 0x1f8ff)
|
|
49
|
-
return 2; // Supplemental Arrows-C
|
|
50
|
-
if (codePoint >= 0x1f900 && codePoint <= 0x1f9ff)
|
|
51
|
-
return 2; // Supplemental Symbols and Pictographs
|
|
52
|
-
if (codePoint >= 0x1fa00 && codePoint <= 0x1faff)
|
|
53
|
-
return 2; // Chess Symbols, Symbols and Pictographs Extended-A
|
|
54
|
-
// NOTE: 0x2300-0x23ff (Misc Technical), 0x2600-0x26ff (Misc Symbols),
|
|
55
|
-
// and 0x2700-0x27bf (Dingbats) are mostly "Ambiguous" width — render as
|
|
56
|
-
// 1 column in non-CJK terminal locales (e.g. ❯, ⌘, ★, ♦). But a handful
|
|
57
|
-
// of dingbats have Emoji_Presentation=Yes and render as 2 cols everywhere.
|
|
58
|
-
if (codePoint === 0x2705 || // ✅ white heavy check mark
|
|
59
|
-
codePoint === 0x270a || // ✊ raised fist
|
|
60
|
-
codePoint === 0x270b || // ✋ raised hand
|
|
61
|
-
codePoint === 0x2728 || // ✨ sparkles
|
|
62
|
-
codePoint === 0x274c || // ❌ cross mark
|
|
63
|
-
codePoint === 0x274e || // ❎ negative squared cross mark
|
|
64
|
-
(codePoint >= 0x2753 && codePoint <= 0x2755) || // ❓❔❕
|
|
65
|
-
codePoint === 0x2757 || // ❗ heavy exclamation mark
|
|
66
|
-
(codePoint >= 0x2795 && codePoint <= 0x2797) || // ➕➖➗
|
|
67
|
-
codePoint === 0x27b0 || // ➰ curly loop
|
|
68
|
-
codePoint === 0x27bf // ➿ double curly loop
|
|
69
|
-
)
|
|
70
|
-
return 2;
|
|
71
|
-
// Regional indicator symbols (flag emoji components)
|
|
72
|
-
if (codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff)
|
|
73
|
-
return 2;
|
|
74
|
-
// CJK Unified Ideographs
|
|
75
|
-
if (codePoint >= 0x4e00 && codePoint <= 0x9fff)
|
|
76
|
-
return 2;
|
|
77
|
-
// CJK Unified Ideographs Extension A
|
|
78
|
-
if (codePoint >= 0x3400 && codePoint <= 0x4dbf)
|
|
79
|
-
return 2;
|
|
80
|
-
// Hangul Syllables
|
|
81
|
-
if (codePoint >= 0xac00 && codePoint <= 0xd7af)
|
|
82
|
-
return 2;
|
|
83
|
-
// CJK Unified Ideographs Extension B-F and other CJK blocks
|
|
84
|
-
if (codePoint >= 0x20000 && codePoint <= 0x2ebef)
|
|
85
|
-
return 2;
|
|
86
|
-
// Fullwidth ASCII variants
|
|
87
|
-
if (codePoint >= 0xff01 && codePoint <= 0xff5e)
|
|
88
|
-
return 2;
|
|
89
|
-
// Fullwidth bracket forms
|
|
90
|
-
if (codePoint >= 0xff5f && codePoint <= 0xff60)
|
|
91
|
-
return 2;
|
|
92
|
-
// Fullwidth symbol variants
|
|
93
|
-
if (codePoint >= 0xffe0 && codePoint <= 0xffe6)
|
|
94
|
-
return 2;
|
|
95
|
-
// Japanese hiragana and katakana
|
|
96
|
-
if (codePoint >= 0x3040 && codePoint <= 0x309f)
|
|
97
|
-
return 2;
|
|
98
|
-
if (codePoint >= 0x30a0 && codePoint <= 0x30ff)
|
|
99
|
-
return 2;
|
|
100
|
-
// CJK symbols and punctuation
|
|
101
|
-
if (codePoint >= 0x3000 && codePoint <= 0x303f)
|
|
102
|
-
return 2;
|
|
103
|
-
// Enclosed CJK letters and months
|
|
104
|
-
if (codePoint >= 0x3200 && codePoint <= 0x32ff)
|
|
105
|
-
return 2;
|
|
106
|
-
// CJK compatibility
|
|
107
|
-
if (codePoint >= 0x3300 && codePoint <= 0x33ff)
|
|
108
|
-
return 2;
|
|
109
|
-
// Hangul Jamo
|
|
110
|
-
if (codePoint >= 0x1100 && codePoint <= 0x11ff)
|
|
111
|
-
return 2;
|
|
112
|
-
// Hangul compatibility Jamo
|
|
113
|
-
if (codePoint >= 0x3130 && codePoint <= 0x318f)
|
|
114
|
-
return 2;
|
|
115
|
-
return 1;
|
|
25
|
+
return stringWidth(String.fromCodePoint(codePoint));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Width of one grapheme cluster in terminal columns. Handles ZWJ sequences,
|
|
29
|
+
* regional-indicator flags, skin-tone modifiers, and VS16 emoji presentation.
|
|
30
|
+
*/
|
|
31
|
+
export function clusterWidth(cluster) {
|
|
32
|
+
return stringWidth(cluster);
|
|
33
|
+
}
|
|
34
|
+
/** Strip SGR (color/style) sequences from a string. */
|
|
35
|
+
function stripSGR(str) {
|
|
36
|
+
return str.replace(/\x1b\[[^m]*m/g, "");
|
|
116
37
|
}
|
|
117
38
|
/**
|
|
118
39
|
* Measure visible string length in terminal columns.
|
|
119
|
-
* Excludes SGR (color/style) sequences and
|
|
40
|
+
* Excludes SGR (color/style) sequences, and counts each grapheme cluster
|
|
41
|
+
* (emoji, CJK, combining marks) as one terminal-visible unit.
|
|
120
42
|
*/
|
|
121
43
|
export function visibleLen(str) {
|
|
122
|
-
|
|
123
|
-
const cleanStr = str.replace(/\x1b\[[^m]*m/g, "");
|
|
124
|
-
let width = 0;
|
|
125
|
-
for (const char of cleanStr) {
|
|
126
|
-
width += charWidth(char.codePointAt(0) ?? 0);
|
|
127
|
-
}
|
|
128
|
-
return width;
|
|
44
|
+
return stringWidth(stripSGR(str));
|
|
129
45
|
}
|
|
130
46
|
/**
|
|
131
47
|
* Truncate a string to fit within `maxWidth` visible columns.
|
|
132
|
-
*
|
|
48
|
+
* Iterates by grapheme cluster so emoji sequences (ZWJ, flags, VS16) are
|
|
49
|
+
* kept intact rather than split mid-cluster. Appends `…` if truncated.
|
|
133
50
|
*/
|
|
134
51
|
export function truncateToWidth(str, maxWidth) {
|
|
135
|
-
const clean = str
|
|
52
|
+
const clean = stripSGR(str);
|
|
136
53
|
if (maxWidth <= 0)
|
|
137
54
|
return "";
|
|
138
|
-
|
|
139
|
-
let fullWidth = 0;
|
|
140
|
-
for (const char of clean) {
|
|
141
|
-
fullWidth += charWidth(char.codePointAt(0) ?? 0);
|
|
142
|
-
}
|
|
143
|
-
if (fullWidth <= maxWidth)
|
|
55
|
+
if (visibleLen(clean) <= maxWidth)
|
|
144
56
|
return clean;
|
|
145
|
-
// String doesn't fit — truncate with "…"
|
|
146
|
-
// At maxWidth=1 the ellipsis alone fills the budget.
|
|
147
57
|
if (maxWidth === 1)
|
|
148
58
|
return "…";
|
|
149
|
-
// Reserve 1 column for "…", so target content width is maxWidth - 1
|
|
150
59
|
const target = maxWidth - 1;
|
|
151
60
|
let width = 0;
|
|
152
|
-
let
|
|
153
|
-
for (const
|
|
154
|
-
const cw =
|
|
61
|
+
let out = "";
|
|
62
|
+
for (const { segment } of GRAPHEME_SEGMENTER.segment(clean)) {
|
|
63
|
+
const cw = clusterWidth(segment);
|
|
155
64
|
if (width + cw > target)
|
|
156
65
|
break;
|
|
157
66
|
width += cw;
|
|
158
|
-
|
|
67
|
+
out += segment;
|
|
159
68
|
}
|
|
160
|
-
|
|
161
|
-
// rather than emit a character that would overflow the budget.
|
|
162
|
-
if (i === 0)
|
|
69
|
+
if (out === "")
|
|
163
70
|
return "…";
|
|
164
|
-
return
|
|
71
|
+
return out + "…";
|
|
165
72
|
}
|
|
166
73
|
/** Truncate to visible width while preserving SGR sequences — use when
|
|
167
74
|
* input carries color/bold codes. `truncateToWidth` strips them. */
|
|
@@ -173,43 +80,57 @@ export function truncateAnsiToWidth(str, maxWidth) {
|
|
|
173
80
|
if (maxWidth === 1)
|
|
174
81
|
return "…";
|
|
175
82
|
const target = maxWidth - 1;
|
|
83
|
+
// Walk the string preserving SGR escapes in-place; buffer text between
|
|
84
|
+
// escapes and segment it into graphemes to count width correctly.
|
|
176
85
|
let width = 0;
|
|
177
86
|
let out = "";
|
|
87
|
+
let buf = "";
|
|
178
88
|
let i = 0;
|
|
89
|
+
const flushBuf = () => {
|
|
90
|
+
if (!buf)
|
|
91
|
+
return false;
|
|
92
|
+
for (const { segment } of GRAPHEME_SEGMENTER.segment(buf)) {
|
|
93
|
+
const cw = clusterWidth(segment);
|
|
94
|
+
if (width + cw > target) {
|
|
95
|
+
buf = "";
|
|
96
|
+
return true; // budget exhausted
|
|
97
|
+
}
|
|
98
|
+
width += cw;
|
|
99
|
+
out += segment;
|
|
100
|
+
}
|
|
101
|
+
buf = "";
|
|
102
|
+
return false;
|
|
103
|
+
};
|
|
179
104
|
while (i < str.length) {
|
|
180
105
|
if (str[i] === "\x1b" && str[i + 1] === "[") {
|
|
181
106
|
const end = str.indexOf("m", i);
|
|
182
107
|
if (end !== -1) {
|
|
108
|
+
if (flushBuf())
|
|
109
|
+
break;
|
|
183
110
|
out += str.slice(i, end + 1);
|
|
184
111
|
i = end + 1;
|
|
185
112
|
continue;
|
|
186
113
|
}
|
|
187
114
|
}
|
|
188
115
|
const cp = str.codePointAt(i) ?? 0;
|
|
189
|
-
const cw = charWidth(cp);
|
|
190
|
-
if (width + cw > target)
|
|
191
|
-
break;
|
|
192
116
|
const chLen = cp > 0xffff ? 2 : 1;
|
|
193
|
-
|
|
194
|
-
width += cw;
|
|
117
|
+
buf += str.slice(i, i + chLen);
|
|
195
118
|
i += chLen;
|
|
196
119
|
}
|
|
120
|
+
flushBuf();
|
|
197
121
|
return out + "\x1b[0m…";
|
|
198
122
|
}
|
|
199
123
|
/**
|
|
200
124
|
* Pad a string with spaces to fill `targetWidth` visible columns.
|
|
201
|
-
* Accounts for CJK double-width characters.
|
|
202
125
|
*/
|
|
203
126
|
export function padEndToWidth(str, targetWidth) {
|
|
204
127
|
const gap = targetWidth - visibleLen(str);
|
|
205
128
|
return gap > 0 ? str + " ".repeat(gap) : str;
|
|
206
129
|
}
|
|
207
|
-
/** Strip
|
|
130
|
+
/** Strip ANSI escape sequences and carriage returns.
|
|
131
|
+
* Delegates escape handling to the `strip-ansi` package (covers SGR, OSC,
|
|
132
|
+
* CSI, private-mode, 8-bit CSI, and newer variants). `\r` is not an escape
|
|
133
|
+
* but callers rely on it being stripped alongside. */
|
|
208
134
|
export function stripAnsi(str) {
|
|
209
|
-
return str
|
|
210
|
-
.replace(/\x1b\][^\x07]*\x07/g, "") // OSC sequences
|
|
211
|
-
.replace(/\x1b\[[^m]*m/g, "") // SGR (color) sequences
|
|
212
|
-
.replace(/\x1b\[\?[^a-zA-Z]*[a-zA-Z]/g, "") // private mode sequences
|
|
213
|
-
.replace(/\x1b\[[^a-zA-Z]*[a-zA-Z]/g, "") // CSI sequences
|
|
214
|
-
.replace(/\r/g, ""); // carriage returns
|
|
135
|
+
return stripAnsiPkg(str).replace(/\r/g, "");
|
|
215
136
|
}
|
package/dist/utils/markdown.js
CHANGED
|
@@ -269,12 +269,20 @@ export class MarkdownRenderer {
|
|
|
269
269
|
const separatorWidth = (numCols - 1) * 3;
|
|
270
270
|
const tableWidth = Math.max(10, this.width - 2);
|
|
271
271
|
const availableWidth = tableWidth - separatorWidth;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
// Shrink the widest column one step at a time until the table fits.
|
|
273
|
+
// Preserves natural width on narrow columns — proportional scaling
|
|
274
|
+
// over-truncates when only one column is oversized.
|
|
275
|
+
let total = colWidths.reduce((a, b) => a + b, 0);
|
|
276
|
+
while (total > availableWidth && availableWidth > numCols) {
|
|
277
|
+
let maxIdx = 0;
|
|
278
|
+
for (let c = 1; c < numCols; c++) {
|
|
279
|
+
if (colWidths[c] > colWidths[maxIdx])
|
|
280
|
+
maxIdx = c;
|
|
277
281
|
}
|
|
282
|
+
if (colWidths[maxIdx] <= 1)
|
|
283
|
+
break;
|
|
284
|
+
colWidths[maxIdx]--;
|
|
285
|
+
total--;
|
|
278
286
|
}
|
|
279
287
|
// Render rows
|
|
280
288
|
const hasHeader = sepIdx.includes(1) && dataRows.length > 1;
|
|
@@ -287,9 +295,13 @@ export class MarkdownRenderer {
|
|
|
287
295
|
const cells = row.map((cell, c) => {
|
|
288
296
|
const w = colWidths[c];
|
|
289
297
|
const rendered = this.renderInline(cell);
|
|
290
|
-
|
|
298
|
+
// Truncation can yield width < w when a CJK double-width char
|
|
299
|
+
// won't fit the remaining budget — always re-pad to keep cells
|
|
300
|
+
// aligned with the border grid.
|
|
301
|
+
const clipped = visibleLen(rendered) > w
|
|
291
302
|
? truncateAnsiToWidth(rendered, w)
|
|
292
|
-
:
|
|
303
|
+
: rendered;
|
|
304
|
+
const text = padEndToWidth(clipped, w);
|
|
293
305
|
return isHeader ? `${p.bold}${text}${p.reset}` : text;
|
|
294
306
|
});
|
|
295
307
|
this.writeLine(`${p.dim}│${p.reset} ${cells.join(` ${p.dim}│${p.reset} `)} ${p.dim}│${p.reset}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "A shell-first terminal where AI is one keystroke away",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core.js",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"types": "./dist/extensions/index.d.ts",
|
|
35
35
|
"default": "./dist/extensions/index.js"
|
|
36
36
|
},
|
|
37
|
+
"./shell": {
|
|
38
|
+
"types": "./dist/shell/shell.d.ts",
|
|
39
|
+
"default": "./dist/shell/shell.js"
|
|
40
|
+
},
|
|
37
41
|
"./utils/stream-transform": {
|
|
38
42
|
"types": "./dist/utils/stream-transform.d.ts",
|
|
39
43
|
"default": "./dist/utils/stream-transform.js"
|
|
@@ -122,6 +126,8 @@
|
|
|
122
126
|
"marked": "^17.0.6",
|
|
123
127
|
"node-pty": "^1.2.0-beta.12",
|
|
124
128
|
"openai": "^6.34.0",
|
|
129
|
+
"string-width": "^8.2.0",
|
|
130
|
+
"strip-ansi": "^7.2.0",
|
|
125
131
|
"tsx": "^4.19.0"
|
|
126
132
|
},
|
|
127
133
|
"devDependencies": {
|