pi-agent-flow 0.2.5 → 0.2.7

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,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-flow",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Flow-state delegation extension for Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/render-utils.ts CHANGED
@@ -84,15 +84,100 @@ export function formatExpandedStats(usage: Partial<UsageStats>, model?: string):
84
84
  return { stats, contextTokens };
85
85
  }
86
86
 
87
- export function truncateChars(text: string, max: number): string {
88
- if (text.length <= max) return text;
87
+ /** Regex matching ANSI escape sequences. */
88
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
89
+
90
+ /** Return the visible (ANSI-stripped) character count. */
91
+ export function visibleLength(text: string): number {
92
+ return text.replace(ANSI_RE, "").length;
93
+ }
94
+
95
+ /**
96
+ * Truncate an ANSI-colored string to at most `max` visible characters,
97
+ * preserving ANSI codes in the kept portions and appending a reset before
98
+ * the ellipsis so colors don't bleed.
99
+ */
100
+ function truncateAnsi(text: string, max: number): string {
101
+ if (visibleLength(text) <= max) return text;
102
+
89
103
  const head = Math.ceil(max * 0.6);
90
- const tail = max - head - 3;
91
- return text.slice(0, head) + " ... " + text.slice(-tail);
104
+ const tail = max - head - 5; // 5 = " ... ".length
105
+
106
+ // Walk through the string, collecting raw chars until we've consumed
107
+ // `count` visible characters. ANSI sequences are copied through without
108
+ // counting toward the limit.
109
+ function takeVisible(src: string, count: number): { raw: string; consumed: number } {
110
+ let raw = "";
111
+ let visible = 0;
112
+ let i = 0;
113
+ while (i < src.length && visible < count) {
114
+ // Check for ANSI escape sequence at current position
115
+ if (src[i] === "\x1b" && src[i + 1] === "[") {
116
+ const end = src.indexOf("m", i + 2);
117
+ if (end !== -1) {
118
+ raw += src.slice(i, end + 1);
119
+ i = end + 1;
120
+ continue;
121
+ }
122
+ }
123
+ raw += src[i];
124
+ visible++;
125
+ i++;
126
+ }
127
+ return { raw, consumed: visible };
128
+ }
129
+
130
+ // Take head from the start
131
+ const headResult = takeVisible(text, head);
132
+
133
+ // Take tail from the end (walk backwards)
134
+ function takeVisibleFromEnd(src: string, count: number): string {
135
+ let visible = 0;
136
+ let i = src.length - 1;
137
+ while (i >= 0 && visible < count) {
138
+ // Check if current char is end of an ANSI sequence
139
+ if (src[i] === "m") {
140
+ const escStart = src.lastIndexOf("\x1b[", i);
141
+ if (escStart !== -1 && escStart < i) {
142
+ // This is an ANSI sequence — don't count, skip past it
143
+ i = escStart - 1;
144
+ continue;
145
+ }
146
+ }
147
+ visible++;
148
+ i--;
149
+ }
150
+ // Extract the raw substring from (i+1) to end, including any trailing ANSI
151
+ return src.slice(i + 1);
152
+ }
153
+
154
+ const tailRaw = takeVisibleFromEnd(text, tail);
155
+
156
+ return headResult.raw + "\x1b[39m ... " + tailRaw;
157
+ }
158
+
159
+ export function truncateChars(text: string, max: number): string {
160
+ if (visibleLength(text) <= max) return text;
161
+ return truncateAnsi(text, max);
92
162
  }
93
163
 
94
164
  export function tailText(text: string, max: number): string {
95
165
  const flat = text.replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
96
- if (flat.length <= max) return flat;
97
- return flat.slice(-max);
166
+ if (visibleLength(flat) <= max) return flat;
167
+
168
+ // Take last `max` visible characters from the end
169
+ let visible = 0;
170
+ let i = flat.length - 1;
171
+ while (i >= 0 && visible < max) {
172
+ if (flat[i] === "m") {
173
+ const escStart = flat.lastIndexOf("\x1b[", i);
174
+ if (escStart !== -1 && escStart < i) {
175
+ i = escStart - 1;
176
+ continue;
177
+ }
178
+ }
179
+ visible++;
180
+ i--;
181
+ }
182
+ return flat.slice(i + 1);
98
183
  }
package/render.ts CHANGED
@@ -38,7 +38,7 @@ function formatFlowToolCall(toolName: string, args: Record<string, unknown>, fg:
38
38
 
39
39
  switch (toolName) {
40
40
  case "bash": {
41
- const cmd = (args.command as string) || "...";
41
+ const cmd = ((args.command as string) || "...").replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
42
42
  return fg("muted", "$ ") + fg("toolOutput", cmd);
43
43
  }
44
44
  case "read": {