@iinm/plain-agent 1.10.5 → 1.10.6
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
package/src/cli/formatter.mjs
CHANGED
|
@@ -428,6 +428,21 @@ export async function printMessage(message) {
|
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Convert **bold** Markdown to ANSI bold terminal escape codes.
|
|
433
|
+
* Only matches when ** is preceded by whitespace or line start
|
|
434
|
+
* and followed by whitespace, line end, or punctuation — so inline
|
|
435
|
+
* code like `**bold**` is left untouched.
|
|
436
|
+
* @param {string} text
|
|
437
|
+
* @returns {string}
|
|
438
|
+
*/
|
|
439
|
+
export function applyInlineMarkdown(text) {
|
|
440
|
+
return text.replace(
|
|
441
|
+
/(?<=\s|^)\*\*(.+?)\*\*(?=[\s.,;:!?)〕)】」』]|$)/g,
|
|
442
|
+
(_, c) => styleText("bold", c),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
431
446
|
/**
|
|
432
447
|
* Format markdown table lines with aligned columns.
|
|
433
448
|
* Input lines may have leading/trailing pipes.
|
package/src/cli/interactive.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { createInterruptTransform } from "./interruptTransform.mjs";
|
|
23
23
|
import { createMuteTransform } from "./muteTransform.mjs";
|
|
24
24
|
import { createPasteHandler } from "./pasteTransform.mjs";
|
|
25
|
-
import {
|
|
25
|
+
import { createStreamFormatter } from "./streamFormatter.mjs";
|
|
26
26
|
|
|
27
27
|
const HELP_MESSAGE = [
|
|
28
28
|
"Commands:",
|
|
@@ -130,8 +130,8 @@ export function startInteractiveSession({
|
|
|
130
130
|
*/
|
|
131
131
|
let voice = null;
|
|
132
132
|
|
|
133
|
-
// Create the
|
|
134
|
-
const
|
|
133
|
+
// Create the stream buffer instance for this session
|
|
134
|
+
const streamBuffer = createStreamBuffer();
|
|
135
135
|
|
|
136
136
|
// Parse the voice toggle key once at startup so misconfiguration fails
|
|
137
137
|
// loudly instead of silently falling back.
|
|
@@ -494,7 +494,7 @@ export function startInteractiveSession({
|
|
|
494
494
|
);
|
|
495
495
|
}
|
|
496
496
|
} else if (partialContent.type === "text") {
|
|
497
|
-
|
|
497
|
+
streamBuffer.feed(partialContent.content);
|
|
498
498
|
} else {
|
|
499
499
|
process.stdout.write(partialContent.content);
|
|
500
500
|
}
|
|
@@ -502,9 +502,11 @@ export function startInteractiveSession({
|
|
|
502
502
|
if (partialContent.position === "stop") {
|
|
503
503
|
if (partialContent.type === "tool_use") {
|
|
504
504
|
process.stdout.write(
|
|
505
|
-
`\r\x1b[K${styleText("gray", `<${partialContent.type}>`)}
|
|
505
|
+
`\r\x1b[K${styleText("gray", `<${partialContent.type}>`)}\n`,
|
|
506
506
|
);
|
|
507
507
|
} else {
|
|
508
|
+
// Flush any buffered text before printing the closing tag
|
|
509
|
+
streamBuffer.forceFlush();
|
|
508
510
|
console.log(styleText("gray", `\n</${partialContent.type}>`));
|
|
509
511
|
}
|
|
510
512
|
}
|
|
@@ -556,8 +558,8 @@ export function startInteractiveSession({
|
|
|
556
558
|
});
|
|
557
559
|
|
|
558
560
|
agentEventEmitter.on("turnEnd", async () => {
|
|
559
|
-
// Flush any remaining
|
|
560
|
-
|
|
561
|
+
// Flush any remaining stream buffer content
|
|
562
|
+
streamBuffer.forceFlush();
|
|
561
563
|
|
|
562
564
|
const err = notify(notifyCmd);
|
|
563
565
|
if (err) {
|
|
@@ -584,21 +586,20 @@ export function startInteractiveSession({
|
|
|
584
586
|
}
|
|
585
587
|
|
|
586
588
|
/**
|
|
587
|
-
* Creates a
|
|
588
|
-
*
|
|
589
|
-
* Thin shell: delegates pure logic to createTableDetector and handles I/O.
|
|
589
|
+
* Creates a stream buffer for formatting streaming text output.
|
|
590
|
+
* Thin shell: delegates pure logic to createStreamFormatter and handles I/O.
|
|
590
591
|
*/
|
|
591
|
-
function
|
|
592
|
-
const
|
|
592
|
+
function createStreamBuffer() {
|
|
593
|
+
const formatter = createStreamFormatter();
|
|
593
594
|
|
|
594
595
|
function feed(/** @type {string} */ chunk) {
|
|
595
|
-
const { output, warnings } =
|
|
596
|
+
const { output, warnings } = formatter.feed(chunk);
|
|
596
597
|
for (const s of output) process.stdout.write(s);
|
|
597
598
|
for (const w of warnings) console.error(styleText("yellow", w));
|
|
598
599
|
}
|
|
599
600
|
|
|
600
601
|
function forceFlush() {
|
|
601
|
-
const { output, warnings } =
|
|
602
|
+
const { output, warnings } = formatter.forceFlush();
|
|
602
603
|
for (const s of output) process.stdout.write(s);
|
|
603
604
|
for (const w of warnings) console.error(styleText("yellow", w));
|
|
604
605
|
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import { formatMarkdownTable } from "./formatter.mjs";
|
|
1
|
+
import { applyInlineMarkdown, formatMarkdownTable } from "./formatter.mjs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {{ output: string[], warnings: string[] }}
|
|
4
|
+
* @typedef {{ output: string[], warnings: string[] }} StreamFormatterResult
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Creates a
|
|
9
|
-
* in
|
|
8
|
+
* Creates a stream formatter for formatting streaming text output
|
|
9
|
+
* in a terminal. Applies **bold** Markdown styling
|
|
10
|
+
* on completed lines, and detects + formats markdown tables.
|
|
11
|
+
* This is a pure logic module with no I/O side effects.
|
|
12
|
+
*
|
|
13
|
+
* All output is deferred until line completion (\n or forceFlush),
|
|
14
|
+
* so inline Markdown patterns spanning chunk boundaries are handled
|
|
15
|
+
* correctly without special boundary-detection logic.
|
|
10
16
|
*
|
|
11
17
|
* @param {(lines: string[], maxWidth?: number) => string} [formatTable=formatMarkdownTable] - Table formatting function (injectable for testing)
|
|
12
18
|
* @param {number} [maxWidth] - Maximum terminal display width (defaults to process.stdout.columns - 4 or 80)
|
|
13
|
-
* @returns {{ feed: (chunk: string) =>
|
|
19
|
+
* @returns {{ feed: (chunk: string) => StreamFormatterResult, forceFlush: () => StreamFormatterResult }}
|
|
14
20
|
*/
|
|
15
|
-
export function
|
|
21
|
+
export function createStreamFormatter(
|
|
16
22
|
formatTable = formatMarkdownTable,
|
|
17
23
|
maxWidth = process.stdout.columns ? process.stdout.columns - 4 : 80,
|
|
18
24
|
) {
|
|
@@ -25,9 +31,9 @@ export function createTableDetector(
|
|
|
25
31
|
const MAX_TABLE_LINES = 200;
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
|
-
* Feed a text chunk to the
|
|
34
|
+
* Feed a text chunk to the formatter.
|
|
29
35
|
* @param {string} chunk
|
|
30
|
-
* @returns {
|
|
36
|
+
* @returns {StreamFormatterResult}
|
|
31
37
|
*/
|
|
32
38
|
function feed(chunk) {
|
|
33
39
|
if (chunk.length === 0) return { output: [], warnings: [] };
|
|
@@ -48,19 +54,12 @@ export function createTableDetector(
|
|
|
48
54
|
warnings.push(...result.warnings);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
// If not buffering a table and pendingLine has no pipe, output immediately
|
|
52
|
-
// This ensures non-table text is streamed without delay
|
|
53
|
-
if (tableLines.length === 0 && !pendingLine.includes("|")) {
|
|
54
|
-
output.push(pendingLine);
|
|
55
|
-
pendingLine = "";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
57
|
return { output, warnings };
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/**
|
|
62
61
|
* Force flush any pending content (call on turn end).
|
|
63
|
-
* @returns {
|
|
62
|
+
* @returns {StreamFormatterResult}
|
|
64
63
|
*/
|
|
65
64
|
function forceFlush() {
|
|
66
65
|
/** @type {string[]} */
|
|
@@ -68,14 +67,11 @@ export function createTableDetector(
|
|
|
68
67
|
/** @type {string[]} */
|
|
69
68
|
const warnings = [];
|
|
70
69
|
|
|
71
|
-
// Process any remaining pending line
|
|
70
|
+
// Process any remaining pending line as a completed line
|
|
72
71
|
if (pendingLine.length > 0) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} else {
|
|
77
|
-
output.push(pendingLine);
|
|
78
|
-
}
|
|
72
|
+
const result = processLine(pendingLine);
|
|
73
|
+
output.push(...result.output);
|
|
74
|
+
warnings.push(...result.warnings);
|
|
79
75
|
pendingLine = "";
|
|
80
76
|
}
|
|
81
77
|
const flushResult = flushTable();
|
|
@@ -87,30 +83,33 @@ export function createTableDetector(
|
|
|
87
83
|
|
|
88
84
|
/**
|
|
89
85
|
* Process a complete line.
|
|
90
|
-
* @param {string}
|
|
91
|
-
* @returns {
|
|
86
|
+
* @param {string} rawLine - Line (may or may not include trailing newline)
|
|
87
|
+
* @returns {StreamFormatterResult}
|
|
92
88
|
*/
|
|
93
|
-
function processLine(
|
|
89
|
+
function processLine(rawLine) {
|
|
94
90
|
/** @type {string[]} */
|
|
95
91
|
const output = [];
|
|
96
92
|
/** @type {string[]} */
|
|
97
93
|
const warnings = [];
|
|
98
94
|
|
|
99
|
-
// Code block detection
|
|
100
|
-
if (
|
|
95
|
+
// Code block detection (before Markdown conversion — code blocks stay raw)
|
|
96
|
+
if (rawLine.trimStart().startsWith("```")) {
|
|
101
97
|
inCodeBlock = !inCodeBlock;
|
|
102
98
|
const flushResult = flushTable(); // Code block terminates any ongoing table
|
|
103
99
|
output.push(...flushResult.output);
|
|
104
100
|
warnings.push(...flushResult.warnings);
|
|
105
|
-
output.push(
|
|
101
|
+
output.push(rawLine);
|
|
106
102
|
return { output, warnings };
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
if (inCodeBlock) {
|
|
110
|
-
output.push(
|
|
106
|
+
output.push(rawLine);
|
|
111
107
|
return { output, warnings };
|
|
112
108
|
}
|
|
113
109
|
|
|
110
|
+
// Apply inline Markdown styling on completed lines
|
|
111
|
+
const line = applyInlineMarkdown(rawLine);
|
|
112
|
+
|
|
114
113
|
// Table start: line begins with pipe
|
|
115
114
|
if (isTableStart(line)) {
|
|
116
115
|
tableLines.push(line);
|
|
@@ -145,7 +144,7 @@ export function createTableDetector(
|
|
|
145
144
|
|
|
146
145
|
/**
|
|
147
146
|
* Flush table buffer with formatting.
|
|
148
|
-
* @returns {
|
|
147
|
+
* @returns {StreamFormatterResult}
|
|
149
148
|
*/
|
|
150
149
|
function flushTable() {
|
|
151
150
|
if (tableLines.length === 0) return { output: [], warnings: [] };
|
|
@@ -193,7 +192,7 @@ export function createTableDetector(
|
|
|
193
192
|
|
|
194
193
|
/**
|
|
195
194
|
* Flush table buffer without formatting (for oversized tables).
|
|
196
|
-
* @returns {
|
|
195
|
+
* @returns {StreamFormatterResult}
|
|
197
196
|
*/
|
|
198
197
|
function flushTableAsIs() {
|
|
199
198
|
if (tableLines.length === 0) return { output: [], warnings: [] };
|