deepclause-sdk 0.0.88 → 0.1.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/LICENSE +21 -0
- package/README.md +418 -914
- package/dist/agent.d.ts +19 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +221 -4
- package/dist/agent.js.map +1 -1
- package/dist/cli/config.d.ts +323 -176
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +146 -188
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +61 -425
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +184 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/readline.d.ts +6 -0
- package/dist/cli/readline.d.ts.map +1 -0
- package/dist/cli/readline.js +15 -0
- package/dist/cli/readline.js.map +1 -0
- package/dist/cli/repl-bin.d.ts +9 -0
- package/dist/cli/repl-bin.d.ts.map +1 -0
- package/dist/cli/repl-bin.js +16 -0
- package/dist/cli/repl-bin.js.map +1 -0
- package/dist/cli/repl.d.ts +191 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +1232 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/run.d.ts +17 -18
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +196 -183
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/set-backend.d.ts +53 -0
- package/dist/cli/set-backend.d.ts.map +1 -0
- package/dist/cli/set-backend.js +259 -0
- package/dist/cli/set-backend.js.map +1 -0
- package/dist/cli/visualize.d.ts +38 -0
- package/dist/cli/visualize.d.ts.map +1 -0
- package/dist/cli/visualize.js +125 -0
- package/dist/cli/visualize.js.map +1 -0
- package/dist/compiler.d.ts +5 -52
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +4 -768
- package/dist/compiler.js.map +1 -1
- package/dist/index.d.ts +29 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/judge/index.d.ts +9 -0
- package/dist/judge/index.d.ts.map +1 -0
- package/dist/judge/index.js +6 -0
- package/dist/judge/index.js.map +1 -0
- package/dist/judge/jev.d.ts +21 -0
- package/dist/judge/jev.d.ts.map +1 -0
- package/dist/judge/jev.js +218 -0
- package/dist/judge/jev.js.map +1 -0
- package/dist/judge/llm.d.ts +23 -0
- package/dist/judge/llm.d.ts.map +1 -0
- package/dist/judge/llm.js +120 -0
- package/dist/judge/llm.js.map +1 -0
- package/dist/judge/marshal.d.ts +26 -0
- package/dist/judge/marshal.d.ts.map +1 -0
- package/dist/judge/marshal.js +266 -0
- package/dist/judge/marshal.js.map +1 -0
- package/dist/judge/mock.d.ts +28 -0
- package/dist/judge/mock.d.ts.map +1 -0
- package/dist/judge/mock.js +98 -0
- package/dist/judge/mock.js.map +1 -0
- package/dist/judge/types.d.ts +95 -0
- package/dist/judge/types.d.ts.map +1 -0
- package/dist/judge/types.js +23 -0
- package/dist/judge/types.js.map +1 -0
- package/dist/prolog/bridge.d.ts +5 -1
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +29 -1
- package/dist/prolog/bridge.js.map +1 -1
- package/dist/prolog-src/deepclause_analysis.pl +10 -2
- package/dist/prolog-src/deepclause_mi.pl +2522 -118
- package/dist/repl/agent.d.ts +72 -0
- package/dist/repl/agent.d.ts.map +1 -0
- package/dist/repl/agent.js +366 -0
- package/dist/repl/agent.js.map +1 -0
- package/dist/repl/auth.d.ts +45 -0
- package/dist/repl/auth.d.ts.map +1 -0
- package/dist/repl/auth.js +116 -0
- package/dist/repl/auth.js.map +1 -0
- package/dist/repl/backend.d.ts +290 -0
- package/dist/repl/backend.d.ts.map +1 -0
- package/dist/repl/backend.js +370 -0
- package/dist/repl/backend.js.map +1 -0
- package/dist/repl/compaction.d.ts +122 -0
- package/dist/repl/compaction.d.ts.map +1 -0
- package/dist/repl/compaction.js +318 -0
- package/dist/repl/compaction.js.map +1 -0
- package/dist/repl/export.d.ts +30 -0
- package/dist/repl/export.d.ts.map +1 -0
- package/dist/repl/export.js +23 -0
- package/dist/repl/export.js.map +1 -0
- package/dist/repl/frames.d.ts +21 -0
- package/dist/repl/frames.d.ts.map +1 -0
- package/dist/repl/frames.js +79 -0
- package/dist/repl/frames.js.map +1 -0
- package/dist/repl/harness-tools.d.ts +74 -0
- package/dist/repl/harness-tools.d.ts.map +1 -0
- package/dist/repl/harness-tools.js +121 -0
- package/dist/repl/harness-tools.js.map +1 -0
- package/dist/repl/highlight.d.ts +30 -0
- package/dist/repl/highlight.d.ts.map +1 -0
- package/dist/repl/highlight.js +285 -0
- package/dist/repl/highlight.js.map +1 -0
- package/dist/repl/history.d.ts +234 -0
- package/dist/repl/history.d.ts.map +1 -0
- package/dist/repl/history.js +661 -0
- package/dist/repl/history.js.map +1 -0
- package/dist/repl/index.d.ts +120 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +107 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/judge.d.ts +51 -0
- package/dist/repl/judge.d.ts.map +1 -0
- package/dist/repl/judge.js +58 -0
- package/dist/repl/judge.js.map +1 -0
- package/dist/repl/markdown.d.ts +25 -0
- package/dist/repl/markdown.d.ts.map +1 -0
- package/dist/repl/markdown.js +103 -0
- package/dist/repl/markdown.js.map +1 -0
- package/dist/repl/picker.d.ts +34 -0
- package/dist/repl/picker.d.ts.map +1 -0
- package/dist/repl/picker.js +81 -0
- package/dist/repl/picker.js.map +1 -0
- package/dist/repl/project-context.d.ts +16 -0
- package/dist/repl/project-context.d.ts.map +1 -0
- package/dist/repl/project-context.js +23 -0
- package/dist/repl/project-context.js.map +1 -0
- package/dist/repl/render.d.ts +160 -0
- package/dist/repl/render.d.ts.map +1 -0
- package/dist/repl/render.js +715 -0
- package/dist/repl/render.js.map +1 -0
- package/dist/repl/session.d.ts +356 -0
- package/dist/repl/session.d.ts.map +1 -0
- package/dist/repl/session.js +1275 -0
- package/dist/repl/session.js.map +1 -0
- package/dist/repl/status.d.ts +100 -0
- package/dist/repl/status.d.ts.map +1 -0
- package/dist/repl/status.js +218 -0
- package/dist/repl/status.js.map +1 -0
- package/dist/repl/tools.d.ts +53 -0
- package/dist/repl/tools.d.ts.map +1 -0
- package/dist/repl/tools.js +307 -0
- package/dist/repl/tools.js.map +1 -0
- package/dist/repl/toolset.d.ts +32 -0
- package/dist/repl/toolset.d.ts.map +1 -0
- package/dist/repl/toolset.js +82 -0
- package/dist/repl/toolset.js.map +1 -0
- package/dist/repl/tui/editor.d.ts +17 -0
- package/dist/repl/tui/editor.d.ts.map +1 -0
- package/dist/repl/tui/editor.js +39 -0
- package/dist/repl/tui/editor.js.map +1 -0
- package/dist/repl/tui/status-bar.d.ts +26 -0
- package/dist/repl/tui/status-bar.d.ts.map +1 -0
- package/dist/repl/tui/status-bar.js +47 -0
- package/dist/repl/tui/status-bar.js.map +1 -0
- package/dist/repl/tui/theme.d.ts +30 -0
- package/dist/repl/tui/theme.d.ts.map +1 -0
- package/dist/repl/tui/theme.js +81 -0
- package/dist/repl/tui/theme.js.map +1 -0
- package/dist/repl/tui/transcript.d.ts +39 -0
- package/dist/repl/tui/transcript.d.ts.map +1 -0
- package/dist/repl/tui/transcript.js +123 -0
- package/dist/repl/tui/transcript.js.map +1 -0
- package/dist/runner.d.ts +145 -13
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +600 -282
- package/dist/runner.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +56 -32
- package/dist/sdk.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +1373 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +393 -72
- package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
- package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
- package/dist/system/assets/docs/META_PROMPT.md +68 -0
- package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
- package/dist/system/assets/index.d.ts +9 -17
- package/dist/system/assets/index.d.ts.map +1 -1
- package/dist/system/assets/index.js +26 -85
- package/dist/system/assets/index.js.map +1 -1
- package/dist/system/runtime/agentvm-manager.d.ts +9 -2
- package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
- package/dist/system/runtime/agentvm-manager.js +70 -44
- package/dist/system/runtime/agentvm-manager.js.map +1 -1
- package/dist/system/runtime/media.d.ts +23 -0
- package/dist/system/runtime/media.d.ts.map +1 -0
- package/dist/system/runtime/media.js +55 -0
- package/dist/system/runtime/media.js.map +1 -0
- package/dist/system/runtime/shell-limits.d.ts +21 -0
- package/dist/system/runtime/shell-limits.d.ts.map +1 -0
- package/dist/system/runtime/shell-limits.js +39 -0
- package/dist/system/runtime/shell-limits.js.map +1 -0
- package/dist/system/runtime/shell-manager.d.ts +8 -3
- package/dist/system/runtime/shell-manager.d.ts.map +1 -1
- package/dist/system/runtime/shell-manager.js +56 -39
- package/dist/system/runtime/shell-manager.js.map +1 -1
- package/dist/system/runtime/shell-tool-events.js +5 -0
- package/dist/system/runtime/shell-tool-events.js.map +1 -1
- package/dist/system/runtime/token-usage.d.ts +2 -0
- package/dist/system/runtime/token-usage.d.ts.map +1 -1
- package/dist/system/runtime/token-usage.js +7 -0
- package/dist/system/runtime/token-usage.js.map +1 -1
- package/dist/tools.d.ts +3 -21
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -168
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +149 -118
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +19 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +338 -0
- package/dist/validate.js.map +1 -0
- package/dist/visualize/index.d.ts +52 -0
- package/dist/visualize/index.d.ts.map +1 -0
- package/dist/visualize/index.js +189 -0
- package/dist/visualize/index.js.map +1 -0
- package/package.json +17 -15
- package/src/prolog-src/deepclause_analysis.pl +10 -2
- package/src/prolog-src/deepclause_mi.pl +2522 -118
- package/dist/cli/commands.d.ts +0 -42
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -246
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/compile.d.ts +0 -99
- package/dist/cli/compile.d.ts.map +0 -1
- package/dist/cli/compile.js +0 -363
- package/dist/cli/compile.js.map +0 -1
- package/dist/cli/default-skills.d.ts +0 -10
- package/dist/cli/default-skills.d.ts.map +0 -1
- package/dist/cli/default-skills.js +0 -206
- package/dist/cli/default-skills.js.map +0 -1
- package/dist/cli/interactive.d.ts +0 -2
- package/dist/cli/interactive.d.ts.map +0 -1
- package/dist/cli/interactive.js +0 -20
- package/dist/cli/interactive.js.map +0 -1
- package/dist/cli/mcp.d.ts +0 -56
- package/dist/cli/mcp.d.ts.map +0 -1
- package/dist/cli/mcp.js +0 -138
- package/dist/cli/mcp.js.map +0 -1
- package/dist/cli/prompt.d.ts +0 -2
- package/dist/cli/prompt.d.ts.map +0 -1
- package/dist/cli/prompt.js +0 -2
- package/dist/cli/prompt.js.map +0 -1
- package/dist/cli/search.d.ts +0 -19
- package/dist/cli/search.d.ts.map +0 -1
- package/dist/cli/search.js +0 -189
- package/dist/cli/search.js.map +0 -1
- package/dist/cli/tool-args.d.ts +0 -2
- package/dist/cli/tool-args.d.ts.map +0 -1
- package/dist/cli/tool-args.js +0 -39
- package/dist/cli/tool-args.js.map +0 -1
- package/dist/cli/tools.d.ts +0 -36
- package/dist/cli/tools.d.ts.map +0 -1
- package/dist/cli/tools.js +0 -229
- package/dist/cli/tools.js.map +0 -1
- package/dist/cli/tui/index.d.ts +0 -22
- package/dist/cli/tui/index.d.ts.map +0 -1
- package/dist/cli/tui/index.js +0 -29
- package/dist/cli/tui/index.js.map +0 -1
- package/dist/cli/tui.d.ts +0 -167
- package/dist/cli/tui.d.ts.map +0 -1
- package/dist/cli/tui.js +0 -5228
- package/dist/cli/tui.js.map +0 -1
- package/dist/compiler_prompt.d.ts +0 -20
- package/dist/compiler_prompt.d.ts.map +0 -1
- package/dist/compiler_prompt.js +0 -870
- package/dist/compiler_prompt.js.map +0 -1
- package/dist/deepclause-cli.skill +0 -0
- package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
- package/dist/system/assets/compactors/default-memory-compactor.dml +0 -6
- package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
- package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
- package/dist/system/assets/docs/TUI.md +0 -150
- package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
- package/dist/system/assets/skills/conductor.dml +0 -76
- package/dist/system/assets/skills/deep-planner.dml +0 -412
- package/dist/system/assets/skills/plan.dml +0 -429
- package/dist/system/assets/skills/security-planner.dml +0 -337
- package/dist/system/assets/skills/skill-creator.dml +0 -187
- package/dist/system/runtime/catalog-recipes.d.ts +0 -24
- package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
- package/dist/system/runtime/catalog-recipes.js +0 -273
- package/dist/system/runtime/catalog-recipes.js.map +0 -1
- package/dist/system/runtime/catalog-skills.d.ts +0 -50
- package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
- package/dist/system/runtime/catalog-skills.js +0 -152
- package/dist/system/runtime/catalog-skills.js.map +0 -1
- package/dist/system/runtime/conductor.d.ts +0 -105
- package/dist/system/runtime/conductor.d.ts.map +0 -1
- package/dist/system/runtime/conductor.js +0 -732
- package/dist/system/runtime/conductor.js.map +0 -1
- package/dist/system/runtime/console-capture.d.ts +0 -9
- package/dist/system/runtime/console-capture.d.ts.map +0 -1
- package/dist/system/runtime/console-capture.js +0 -67
- package/dist/system/runtime/console-capture.js.map +0 -1
- package/dist/system/runtime/dml-executor.d.ts +0 -52
- package/dist/system/runtime/dml-executor.d.ts.map +0 -1
- package/dist/system/runtime/dml-executor.js +0 -203
- package/dist/system/runtime/dml-executor.js.map +0 -1
- package/dist/system/runtime/runtime-tools.d.ts +0 -31
- package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
- package/dist/system/runtime/runtime-tools.js +0 -312
- package/dist/system/runtime/runtime-tools.js.map +0 -1
- package/dist/system/runtime/skill-creator.d.ts +0 -38
- package/dist/system/runtime/skill-creator.d.ts.map +0 -1
- package/dist/system/runtime/skill-creator.js +0 -763
- package/dist/system/runtime/skill-creator.js.map +0 -1
- package/src/prolog-src/deepclause_mi.pl.bak +0 -570
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-oriented REPL renderer.
|
|
3
|
+
*
|
|
4
|
+
* Three modes:
|
|
5
|
+
* - `rich` (default on a TTY): coloured bindings and labels, no live rewriting.
|
|
6
|
+
* - `plain` (default when stdout is not a TTY): one static line per event, no ANSI.
|
|
7
|
+
* - `json`: one JSON object per event for tooling.
|
|
8
|
+
*
|
|
9
|
+
* This is the M1 surface (solutions, output, answer, errors). Live streaming
|
|
10
|
+
* of model text/tool calls is added in M3.
|
|
11
|
+
*/
|
|
12
|
+
import { formatListing } from './highlight.js';
|
|
13
|
+
import { MarkdownStream } from './markdown.js';
|
|
14
|
+
/** Activity events hidden by `--no-events` (output/answer/input/error stay). */
|
|
15
|
+
const ACTIVITY_EVENTS = new Set([
|
|
16
|
+
'task_activity',
|
|
17
|
+
'tool_call',
|
|
18
|
+
'model_call',
|
|
19
|
+
'usage',
|
|
20
|
+
'log',
|
|
21
|
+
'memory_compaction',
|
|
22
|
+
'meta_result',
|
|
23
|
+
]);
|
|
24
|
+
function formatArgs(args) {
|
|
25
|
+
if (!args || Object.keys(args).length === 0) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
const parts = Object.entries(args).map(([key, value]) => {
|
|
29
|
+
const text = (typeof value === 'string' ? value : JSON.stringify(value)).replace(/\s+/g, ' ').trim();
|
|
30
|
+
const compact = text.length > 60 ? `${text.slice(0, 57)}…` : text;
|
|
31
|
+
return `${key}=${compact}`;
|
|
32
|
+
});
|
|
33
|
+
return `(${parts.join(', ')})`;
|
|
34
|
+
}
|
|
35
|
+
/** Max captured output lines previewed under a completed tool call. */
|
|
36
|
+
const TOOL_OUTPUT_LINES = 3;
|
|
37
|
+
/** Max physical lines of a single `output` event before it is collapsed. */
|
|
38
|
+
const OUTPUT_LINES = 24;
|
|
39
|
+
/**
|
|
40
|
+
* Bound a potentially huge `output` event (e.g. a `write/1` of a large term).
|
|
41
|
+
* Keeps the head and tail and collapses the middle; `--verbose` shows it whole.
|
|
42
|
+
*/
|
|
43
|
+
function boundOutput(text, max = OUTPUT_LINES) {
|
|
44
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
45
|
+
if (lines.length <= max) {
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
const head = Math.ceil((max - 1) / 2);
|
|
49
|
+
const tail = max - 1 - head;
|
|
50
|
+
const omitted = lines.length - head - tail;
|
|
51
|
+
return [
|
|
52
|
+
...lines.slice(0, head),
|
|
53
|
+
`… (+${omitted} more line${omitted === 1 ? '' : 's'})`,
|
|
54
|
+
...lines.slice(lines.length - tail),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
/** Pull a few output lines from a tool result (bash stdout/stderr). */
|
|
58
|
+
function extractToolOutput(result) {
|
|
59
|
+
if (!result || typeof result !== 'object')
|
|
60
|
+
return [];
|
|
61
|
+
const r = result;
|
|
62
|
+
const text = typeof r.stdout === 'string' && r.stdout.trim()
|
|
63
|
+
? r.stdout
|
|
64
|
+
: (typeof r.stderr === 'string' ? r.stderr : '');
|
|
65
|
+
if (!text.trim())
|
|
66
|
+
return [];
|
|
67
|
+
return text
|
|
68
|
+
.replace(/\r\n?/g, '\n')
|
|
69
|
+
.split('\n')
|
|
70
|
+
.map((line) => line.replace(/\t/g, ' ').trimEnd())
|
|
71
|
+
.filter((line) => line.trim() !== '');
|
|
72
|
+
}
|
|
73
|
+
/** Word-wrap a single line to `width`, hard-breaking tokens that are longer. */
|
|
74
|
+
function wrapLine(text, width) {
|
|
75
|
+
if (width <= 0 || text.length <= width) {
|
|
76
|
+
return [text];
|
|
77
|
+
}
|
|
78
|
+
const out = [];
|
|
79
|
+
let remaining = text;
|
|
80
|
+
while (remaining.length > width) {
|
|
81
|
+
let breakAt = remaining.lastIndexOf(' ', width);
|
|
82
|
+
if (breakAt <= 0) {
|
|
83
|
+
breakAt = width; // no space: hard-break a long token (URL, path, flag)
|
|
84
|
+
}
|
|
85
|
+
out.push(remaining.slice(0, breakAt));
|
|
86
|
+
remaining = remaining.slice(breakAt).replace(/^ +/, '');
|
|
87
|
+
}
|
|
88
|
+
out.push(remaining);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function truncate(text, max) {
|
|
92
|
+
const singleLine = text.replace(/\s+/g, ' ').trim();
|
|
93
|
+
return singleLine.length > max ? `${singleLine.slice(0, max - 1)}…` : singleLine;
|
|
94
|
+
}
|
|
95
|
+
/** Human-readable wall-clock duration: `450ms`, `1.2s`, `2m05s`. */
|
|
96
|
+
export function formatDuration(ms) {
|
|
97
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
if (ms < 1000) {
|
|
101
|
+
return `${Math.round(ms)}ms`;
|
|
102
|
+
}
|
|
103
|
+
const seconds = ms / 1000;
|
|
104
|
+
if (seconds < 60) {
|
|
105
|
+
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
106
|
+
}
|
|
107
|
+
const minutes = Math.floor(seconds / 60);
|
|
108
|
+
const remainder = Math.round(seconds % 60);
|
|
109
|
+
return `${minutes}m${String(remainder).padStart(2, '0')}s`;
|
|
110
|
+
}
|
|
111
|
+
const ANSI = {
|
|
112
|
+
reset: '\u001b[0m',
|
|
113
|
+
bold: '\u001b[1m',
|
|
114
|
+
dim: '\u001b[2m',
|
|
115
|
+
cyan: '\u001b[36m',
|
|
116
|
+
green: '\u001b[32m',
|
|
117
|
+
red: '\u001b[31m',
|
|
118
|
+
/** Model reasoning: italic grey, like pi's thinking text. */
|
|
119
|
+
thinking: '\u001b[3;38;5;245m',
|
|
120
|
+
/** DML `output/1` intermediate results. */
|
|
121
|
+
output: '\u001b[38;5;150m',
|
|
122
|
+
/** The final `answer/1`. */
|
|
123
|
+
answer: '\u001b[1;38;5;118m',
|
|
124
|
+
};
|
|
125
|
+
/** True when the terminal advertises 24-bit colour (COLORTERM). */
|
|
126
|
+
const TRUECOLOR = /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? '');
|
|
127
|
+
/**
|
|
128
|
+
* Tool-call panel: a subtle, near-transparent green tint behind activity
|
|
129
|
+
* (truecolor when available, else a 256-colour fallback). Kept light so it
|
|
130
|
+
* reads as a faint wash over the terminal background rather than a solid fill.
|
|
131
|
+
*/
|
|
132
|
+
const TOOL_PANEL_GREEN_BG = TRUECOLOR ? '\u001b[48;2;40;54;44m' : '\u001b[48;5;22m';
|
|
133
|
+
/** Blue tint for `execute` (a goal, not a shell command). */
|
|
134
|
+
const TOOL_PANEL_BLUE_BG = TRUECOLOR ? '\u001b[48;2;34;44;62m' : '\u001b[48;5;24m';
|
|
135
|
+
const TOOL_PANEL_FG = '\u001b[38;5;252m';
|
|
136
|
+
const TOOL_PANEL_ERROR_BG = TRUECOLOR ? '\u001b[48;2;58;36;36m' : '\u001b[48;5;52m';
|
|
137
|
+
const TOOL_PANEL_ERROR_FG = '\u001b[38;5;224m';
|
|
138
|
+
/**
|
|
139
|
+
* Tools whose main argument is a long literal worth showing in full (wrapped
|
|
140
|
+
* and indented under the header), with the panel colour for that tool.
|
|
141
|
+
*/
|
|
142
|
+
const TOOL_DETAIL = {
|
|
143
|
+
bash: { arg: 'command', tone: 'green' },
|
|
144
|
+
execute: { arg: 'goal', tone: 'blue' },
|
|
145
|
+
};
|
|
146
|
+
/** A copy of `args` without `key` (for the compact header of a detail tool). */
|
|
147
|
+
function omitKey(args, key) {
|
|
148
|
+
if (!args)
|
|
149
|
+
return undefined;
|
|
150
|
+
const rest = {};
|
|
151
|
+
for (const [k, v] of Object.entries(args)) {
|
|
152
|
+
if (k !== key)
|
|
153
|
+
rest[k] = v;
|
|
154
|
+
}
|
|
155
|
+
return rest;
|
|
156
|
+
}
|
|
157
|
+
/** Spinner frames for the live model-call indicator. */
|
|
158
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
159
|
+
/** Strip ANSI SGR sequences (for measuring visible width). */
|
|
160
|
+
function stripAnsi(text) {
|
|
161
|
+
return text.replace(/\u001b\[[0-9;]*m/g, '');
|
|
162
|
+
}
|
|
163
|
+
/** Format solution bindings as `Name = value` lines (or `true`). */
|
|
164
|
+
export function formatBindings(bindings) {
|
|
165
|
+
if (bindings.length === 0) {
|
|
166
|
+
return 'true';
|
|
167
|
+
}
|
|
168
|
+
return bindings.map((binding) => `${binding.name} = ${binding.value}`).join('\n');
|
|
169
|
+
}
|
|
170
|
+
/** Max characters of reasoning shown in `summary` mode. */
|
|
171
|
+
const REASONING_SUMMARY_CHARS = 600;
|
|
172
|
+
export class ReplRenderer {
|
|
173
|
+
mode;
|
|
174
|
+
color;
|
|
175
|
+
verbose;
|
|
176
|
+
showStream;
|
|
177
|
+
showEvents;
|
|
178
|
+
reasoningMode;
|
|
179
|
+
markdownEnabled;
|
|
180
|
+
markdown = new MarkdownStream();
|
|
181
|
+
pendingTextEvent;
|
|
182
|
+
width;
|
|
183
|
+
sink;
|
|
184
|
+
errorSink;
|
|
185
|
+
eventSink;
|
|
186
|
+
/** Frame key of the currently open streamed-text region, or null. */
|
|
187
|
+
lastStreamFrame = null;
|
|
188
|
+
/** Reasoning region key and characters shown so far (for `summary` mode). */
|
|
189
|
+
reasoningRegion = '';
|
|
190
|
+
reasoningChars = 0;
|
|
191
|
+
reasoningTruncated = false;
|
|
192
|
+
atLineStart = true;
|
|
193
|
+
/** Last detail body printed for a running call (dedupes start/running). */
|
|
194
|
+
lastToolDetail;
|
|
195
|
+
/** Live model-call indicator state. */
|
|
196
|
+
spinnerTimer;
|
|
197
|
+
spinnerFrame = 0;
|
|
198
|
+
spinnerPrefix = '';
|
|
199
|
+
spinnerLabel = '';
|
|
200
|
+
spinnerVisible = false;
|
|
201
|
+
constructor(options = {}) {
|
|
202
|
+
this.mode = options.mode ?? 'plain';
|
|
203
|
+
this.color = (options.color ?? true) && this.mode === 'rich';
|
|
204
|
+
this.verbose = options.verbose ?? false;
|
|
205
|
+
this.showStream = options.stream ?? true;
|
|
206
|
+
this.showEvents = options.events ?? true;
|
|
207
|
+
this.reasoningMode = options.reasoning ?? 'full';
|
|
208
|
+
this.markdownEnabled = options.markdown ?? true;
|
|
209
|
+
this.width = options.width ?? (process.stdout.columns || 80);
|
|
210
|
+
this.sink = options.sink ?? ((text) => process.stdout.write(text));
|
|
211
|
+
this.errorSink = options.errorSink ?? ((text) => process.stderr.write(text));
|
|
212
|
+
this.eventSink = options.onEvent;
|
|
213
|
+
}
|
|
214
|
+
/** Replace the output sinks (used when moving into a TUI transcript). */
|
|
215
|
+
setSink(sink, errorSink) {
|
|
216
|
+
this.sink = sink;
|
|
217
|
+
if (errorSink)
|
|
218
|
+
this.errorSink = errorSink;
|
|
219
|
+
}
|
|
220
|
+
/** Update the rendering width (TUI resize). */
|
|
221
|
+
setWidth(width) {
|
|
222
|
+
if (width > 0)
|
|
223
|
+
this.width = width;
|
|
224
|
+
}
|
|
225
|
+
/** Clean up and syntax-highlight a Prolog listing for display. */
|
|
226
|
+
listing(text) {
|
|
227
|
+
return formatListing(text, { color: this.color && this.mode === 'rich' });
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Suppress `input_required` prompt lines. The TUI editor shows the question in
|
|
231
|
+
* its border, so a transcript line would be a duplicate.
|
|
232
|
+
*/
|
|
233
|
+
setSuppressInputPrompt(suppress) {
|
|
234
|
+
this.suppressInputPrompt = suppress;
|
|
235
|
+
}
|
|
236
|
+
/** Stop the live indicator (call before the renderer goes out of scope). */
|
|
237
|
+
dispose() {
|
|
238
|
+
this.stopSpinner();
|
|
239
|
+
}
|
|
240
|
+
/** Last rendered block group, used to separate tool/text/output blocks. */
|
|
241
|
+
currentBlock = null;
|
|
242
|
+
toolBlockCounter = 0;
|
|
243
|
+
currentToolBlock = null;
|
|
244
|
+
taskBlockCounter = 0;
|
|
245
|
+
currentTaskBlock = null;
|
|
246
|
+
/** When true, `input_required` events are rendered by the input UI instead. */
|
|
247
|
+
suppressInputPrompt = false;
|
|
248
|
+
/** Write a raw chunk to the normal sink. */
|
|
249
|
+
write(text) {
|
|
250
|
+
this.stopSpinner();
|
|
251
|
+
this.sink(text);
|
|
252
|
+
}
|
|
253
|
+
/** Write a line to the normal sink. */
|
|
254
|
+
writeLine(text = '') {
|
|
255
|
+
this.stopSpinner();
|
|
256
|
+
this.flushStream();
|
|
257
|
+
this.sink(`${text}\n`);
|
|
258
|
+
this.lastStreamFrame = null;
|
|
259
|
+
this.atLineStart = true;
|
|
260
|
+
}
|
|
261
|
+
/** Flush any buffered streamed text (e.g. an unterminated markdown line). */
|
|
262
|
+
flush() {
|
|
263
|
+
this.flushStream();
|
|
264
|
+
}
|
|
265
|
+
writeErrorLine(text) {
|
|
266
|
+
this.stopSpinner();
|
|
267
|
+
this.errorSink(`${text}\n`);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The visual block an event belongs to. Events in the same block are printed
|
|
271
|
+
* contiguously; switching blocks inserts one blank line. Returns null for
|
|
272
|
+
* events that do not participate (solutions, spinners, metadata).
|
|
273
|
+
*/
|
|
274
|
+
blockKeyFor(event) {
|
|
275
|
+
switch (event.type) {
|
|
276
|
+
case 'stream':
|
|
277
|
+
return `text:${event.frameKind ?? 'run'}@${event.depth ?? 0}`;
|
|
278
|
+
case 'reasoning':
|
|
279
|
+
return event.content ? `reasoning:${event.frameKind ?? 'run'}@${event.depth ?? 0}` : null;
|
|
280
|
+
case 'tool_call': {
|
|
281
|
+
if (this.currentToolBlock === null || event.toolState === 'starting') {
|
|
282
|
+
this.toolBlockCounter += 1;
|
|
283
|
+
this.currentToolBlock = `tool:${this.toolBlockCounter}`;
|
|
284
|
+
}
|
|
285
|
+
return this.currentToolBlock;
|
|
286
|
+
}
|
|
287
|
+
case 'task_activity': {
|
|
288
|
+
if (this.currentTaskBlock === null || event.taskState === 'started') {
|
|
289
|
+
this.taskBlockCounter += 1;
|
|
290
|
+
this.currentTaskBlock = `task:${this.taskBlockCounter}`;
|
|
291
|
+
}
|
|
292
|
+
return this.currentTaskBlock;
|
|
293
|
+
}
|
|
294
|
+
case 'output':
|
|
295
|
+
case 'answer':
|
|
296
|
+
case 'error':
|
|
297
|
+
return 'output';
|
|
298
|
+
case 'memory_compaction':
|
|
299
|
+
return 'compaction';
|
|
300
|
+
default:
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** Insert a separating blank line when the block changes. */
|
|
305
|
+
beginBlock(key) {
|
|
306
|
+
if (!key)
|
|
307
|
+
return;
|
|
308
|
+
if (this.currentBlock !== null && this.currentBlock !== key) {
|
|
309
|
+
this.writeLine('');
|
|
310
|
+
}
|
|
311
|
+
this.currentBlock = key;
|
|
312
|
+
}
|
|
313
|
+
/** Render one runtime event. */
|
|
314
|
+
render(event) {
|
|
315
|
+
this.eventSink?.(event);
|
|
316
|
+
if (!this.showStream && (event.type === 'stream' || event.type === 'reasoning')) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!this.showEvents && ACTIVITY_EVENTS.has(event.type)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (this.mode === 'json') {
|
|
323
|
+
this.sink(`${JSON.stringify(event)}\n`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// The top-level prompt task and the meta frame simply echo work that is
|
|
327
|
+
// already shown by the user message and the streamed authoring text, and
|
|
328
|
+
// skipped compactions are noise. Hide them unless verbose; the status
|
|
329
|
+
// tracker still observes every event via `eventSink`.
|
|
330
|
+
if (event.type === 'task_activity'
|
|
331
|
+
&& !event.frameKind) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (event.type === 'memory_compaction' && event.compactionAction === 'skipped' && !this.verbose) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (event.type !== 'stream' && event.type !== 'reasoning') {
|
|
338
|
+
this.stopSpinner();
|
|
339
|
+
this.flushStream();
|
|
340
|
+
}
|
|
341
|
+
this.beginBlock(this.blockKeyFor(event));
|
|
342
|
+
const prefix = this.frameLabel(event);
|
|
343
|
+
switch (event.type) {
|
|
344
|
+
case 'solution':
|
|
345
|
+
this.solution(event.solution?.bindings ?? [], undefined, prefix,
|
|
346
|
+
// Nested (`execute` child) bindings often carry a whole result term;
|
|
347
|
+
// keep the transcript readable by truncating them.
|
|
348
|
+
event.frameKind === 'nested' ? 160 : undefined);
|
|
349
|
+
break;
|
|
350
|
+
case 'output': {
|
|
351
|
+
// A single event can carry a large multi-line term (`write/1` of an
|
|
352
|
+
// `exec(...)` result). Bound the line count, then fit each line to the
|
|
353
|
+
// terminal width so nothing wraps (which would desync the TUI). Nested
|
|
354
|
+
// activity is truncated to one line each; top-level output wraps.
|
|
355
|
+
const max = event.frameKind === 'nested' ? 6 : OUTPUT_LINES;
|
|
356
|
+
const bounded = this.verbose
|
|
357
|
+
? (event.content ?? '')
|
|
358
|
+
: boundOutput(event.content ?? '', max).join('\n');
|
|
359
|
+
this.writeFrameText(prefix, bounded, ANSI.output, event.frameKind === 'nested');
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case 'answer':
|
|
363
|
+
this.writeFrameText(prefix, event.content ?? '', ANSI.answer, false);
|
|
364
|
+
break;
|
|
365
|
+
case 'model_call':
|
|
366
|
+
this.renderModelCall(event, prefix);
|
|
367
|
+
break;
|
|
368
|
+
case 'stream':
|
|
369
|
+
// Visible model text supersedes the working indicator.
|
|
370
|
+
this.stopSpinner();
|
|
371
|
+
this.writeStream(event, event.content ?? '', 'text');
|
|
372
|
+
break;
|
|
373
|
+
case 'reasoning':
|
|
374
|
+
if (event.content) {
|
|
375
|
+
if (this.writeReasoning(event, event.content)) {
|
|
376
|
+
this.stopSpinner();
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
// Reasoning is hidden (`off`) or truncated (`summary`): commit any
|
|
380
|
+
// visible reasoning, then keep a spinner so the user still sees the
|
|
381
|
+
// model working.
|
|
382
|
+
this.flushStream();
|
|
383
|
+
this.ensureThinkingSpinner(prefix);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
case 'log':
|
|
388
|
+
if (this.verbose) {
|
|
389
|
+
this.writeLine(this.paint(`${prefix}[log] ${event.content ?? ''}`, ANSI.dim));
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
case 'task_activity':
|
|
393
|
+
this.renderTaskActivity(event, prefix);
|
|
394
|
+
break;
|
|
395
|
+
case 'tool_call':
|
|
396
|
+
this.renderToolCall(event, prefix);
|
|
397
|
+
break;
|
|
398
|
+
case 'input_required':
|
|
399
|
+
if (!this.suppressInputPrompt) {
|
|
400
|
+
this.writeLine(this.paint(`${prefix}? ${event.prompt ?? ''}`, ANSI.bold));
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case 'error':
|
|
404
|
+
this.writeErrorLine(this.paint(`${prefix}ERROR: ${event.content ?? ''}`, ANSI.red));
|
|
405
|
+
break;
|
|
406
|
+
case 'usage':
|
|
407
|
+
if (this.verbose && event.usage) {
|
|
408
|
+
this.writeLine(this.paint(`${prefix}[usage] in=${event.usage.inputTokens} out=${event.usage.outputTokens} total=${event.usage.totalTokens}`, ANSI.dim));
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
case 'memory_compaction':
|
|
412
|
+
this.writeLine(this.paint(`${prefix}${event.content ?? 'memory compacted'}`, ANSI.dim));
|
|
413
|
+
break;
|
|
414
|
+
case 'finished':
|
|
415
|
+
case 'meta_result':
|
|
416
|
+
default:
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
renderTaskActivity(event, prefix) {
|
|
421
|
+
const state = event.taskState ?? 'started';
|
|
422
|
+
if (state === 'started') {
|
|
423
|
+
this.writeLine(this.paint(`${prefix}▸ Task ${this.describeTask(event)}`, ANSI.dim));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const ok = state === 'completed';
|
|
427
|
+
this.writeLine(this.paint(`${prefix}${ok ? '✔' : '✖'} Task ${ok ? 'done' : 'failed'}`, ok ? ANSI.dim : ANSI.red));
|
|
428
|
+
}
|
|
429
|
+
describeTask(event) {
|
|
430
|
+
const description = (event.taskDescription ?? '').replace(/\s+/g, ' ').trim();
|
|
431
|
+
if (!description)
|
|
432
|
+
return 'task';
|
|
433
|
+
return description.length > 72 ? `${description.slice(0, 69)}…` : description;
|
|
434
|
+
}
|
|
435
|
+
renderToolCall(event, prefix) {
|
|
436
|
+
const name = event.toolName ?? 'tool';
|
|
437
|
+
const state = event.toolState;
|
|
438
|
+
// Some tools have a long literal argument (a shell script, a DML goal) that
|
|
439
|
+
// is shown in full, wrapped and indented, rather than truncated inline.
|
|
440
|
+
const spec = TOOL_DETAIL[name];
|
|
441
|
+
const rawDetail = spec ? event.toolArgs?.[spec.arg] : undefined;
|
|
442
|
+
const detail = typeof rawDetail === 'string' ? rawDetail : undefined;
|
|
443
|
+
const tone = spec?.tone ?? 'green';
|
|
444
|
+
const args = detail === undefined ? formatArgs(event.toolArgs) : '';
|
|
445
|
+
if (state === 'completed') {
|
|
446
|
+
const duration = typeof event.toolDurationMs === 'number' ? ` · ${formatDuration(event.toolDurationMs)}` : '';
|
|
447
|
+
const summary = event.toolSummary ? ` — ${truncate(event.toolSummary, 80)}` : '';
|
|
448
|
+
this.lastToolDetail = undefined;
|
|
449
|
+
this.writeLine(this.toolPanel(`${prefix} ✔ ${name}${args}${duration}${summary}`, tone));
|
|
450
|
+
// A short tail of the captured output (bash). Full output is --verbose.
|
|
451
|
+
if (!this.verbose) {
|
|
452
|
+
for (const line of this.toolOutputLines(prefix, tone, event.toolResult)) {
|
|
453
|
+
this.writeLine(line);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (state === 'failed') {
|
|
459
|
+
const duration = typeof event.toolDurationMs === 'number' ? ` · ${formatDuration(event.toolDurationMs)}` : '';
|
|
460
|
+
const reason = event.toolError ?? event.toolSummary ?? 'failed';
|
|
461
|
+
this.lastToolDetail = undefined;
|
|
462
|
+
this.writeLine(this.toolPanel(`${prefix} ✖ ${name}${args}${duration} — ${truncate(reason, 120)}`, 'error'));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (detail !== undefined) {
|
|
466
|
+
// A call emits `starting` then `running`; print the body once.
|
|
467
|
+
if (detail !== this.lastToolDetail) {
|
|
468
|
+
this.lastToolDetail = detail;
|
|
469
|
+
const others = formatArgs(omitKey(event.toolArgs, spec.arg));
|
|
470
|
+
this.writeLine(this.toolPanel(`${prefix} ⚙ ${name}${others}`, tone));
|
|
471
|
+
// Print the body on panel lines, word-wrapped to the available width
|
|
472
|
+
// and indented under the header so wrapped bodies stay aligned.
|
|
473
|
+
const indent = ' ';
|
|
474
|
+
const avail = Math.max(20, this.width - stripAnsi(prefix).length - indent.length);
|
|
475
|
+
for (const rawLine of detail.replace(/\r\n?/g, '\n').split('\n')) {
|
|
476
|
+
for (const segment of wrapLine(rawLine, avail)) {
|
|
477
|
+
this.writeLine(this.toolPanel(`${prefix}${indent}${segment}`, tone));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.writeLine(this.toolPanel(`${prefix} ⚙ ${name}${args}`, tone));
|
|
484
|
+
}
|
|
485
|
+
/** Up to TOOL_OUTPUT_LINES captured output lines, as tinted panel lines. */
|
|
486
|
+
toolOutputLines(prefix, tone, result) {
|
|
487
|
+
const lines = extractToolOutput(result);
|
|
488
|
+
if (lines.length === 0) {
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
const indent = ' ';
|
|
492
|
+
const avail = Math.max(20, this.width - stripAnsi(prefix).length - indent.length);
|
|
493
|
+
const shown = lines.slice(-TOOL_OUTPUT_LINES);
|
|
494
|
+
const omitted = lines.length - shown.length;
|
|
495
|
+
const out = [];
|
|
496
|
+
if (omitted > 0) {
|
|
497
|
+
out.push(this.toolPanel(`${prefix}${indent}… (+${omitted} more line${omitted === 1 ? '' : 's'})`, tone));
|
|
498
|
+
}
|
|
499
|
+
for (const line of shown) {
|
|
500
|
+
out.push(this.toolPanel(`${prefix}${indent}${line.length > avail ? `${line.slice(0, avail - 1)}…` : line}`, tone));
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
/** Show/hide the live model-call indicator. */
|
|
505
|
+
renderModelCall(event, prefix) {
|
|
506
|
+
if (event.modelCallState === 'started') {
|
|
507
|
+
this.startSpinner(prefix, 'working…');
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
this.stopSpinner();
|
|
511
|
+
}
|
|
512
|
+
/** Start a `\r`-based spinner on the current line (rich TTY only). */
|
|
513
|
+
startSpinner(prefix, label) {
|
|
514
|
+
if (!this.color || this.mode !== 'rich') {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
this.stopSpinner();
|
|
518
|
+
this.spinnerPrefix = prefix;
|
|
519
|
+
this.spinnerLabel = label;
|
|
520
|
+
this.spinnerFrame = 0;
|
|
521
|
+
this.paintSpinner();
|
|
522
|
+
this.spinnerTimer = setInterval(() => {
|
|
523
|
+
this.spinnerFrame += 1;
|
|
524
|
+
this.paintSpinner();
|
|
525
|
+
}, 90);
|
|
526
|
+
}
|
|
527
|
+
paintSpinner() {
|
|
528
|
+
const frame = SPINNER_FRAMES[this.spinnerFrame % SPINNER_FRAMES.length];
|
|
529
|
+
this.sink(`\r\u001b[K${this.spinnerPrefix}${this.paint(`${frame} ${this.spinnerLabel}`, ANSI.dim)}`);
|
|
530
|
+
this.spinnerVisible = true;
|
|
531
|
+
}
|
|
532
|
+
/** Clear the spinner line and stop its timer (no-op when inactive). */
|
|
533
|
+
stopSpinner() {
|
|
534
|
+
if (this.spinnerTimer) {
|
|
535
|
+
clearInterval(this.spinnerTimer);
|
|
536
|
+
this.spinnerTimer = undefined;
|
|
537
|
+
}
|
|
538
|
+
if (this.spinnerVisible) {
|
|
539
|
+
this.sink('\r\u001b[K');
|
|
540
|
+
this.spinnerVisible = false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* A full-width, tinted line for tool activity (pi-style panel). Falls back to
|
|
545
|
+
* the bare text when colour is disabled. Existing ANSI in `text` is preserved
|
|
546
|
+
* and the visible width (ignoring SGR) is padded out to the terminal width.
|
|
547
|
+
*/
|
|
548
|
+
toolPanel(text, tone) {
|
|
549
|
+
if (!this.color) {
|
|
550
|
+
return text;
|
|
551
|
+
}
|
|
552
|
+
const bg = tone === 'error'
|
|
553
|
+
? TOOL_PANEL_ERROR_BG
|
|
554
|
+
: tone === 'blue'
|
|
555
|
+
? TOOL_PANEL_BLUE_BG
|
|
556
|
+
: TOOL_PANEL_GREEN_BG;
|
|
557
|
+
const fg = tone === 'error' ? TOOL_PANEL_ERROR_FG : TOOL_PANEL_FG;
|
|
558
|
+
const width = this.width - stripAnsi(text).length;
|
|
559
|
+
const pad = width > 0 ? ' '.repeat(width) : '';
|
|
560
|
+
return `${bg}${fg}${text}${pad}${ANSI.reset}`;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Write `text` as frame-prefixed lines that each fit the terminal width:
|
|
564
|
+
* wrap by default, or truncate each physical line (nested activity).
|
|
565
|
+
*/
|
|
566
|
+
writeFrameText(prefix, text, code, truncate) {
|
|
567
|
+
const avail = Math.max(20, this.width - stripAnsi(prefix).length);
|
|
568
|
+
const normalized = text.replace(/\r\n?/g, '\n').replace(/\t/g, ' ');
|
|
569
|
+
for (const rawLine of normalized.split('\n')) {
|
|
570
|
+
const segments = truncate
|
|
571
|
+
? [rawLine.length > avail ? `${rawLine.slice(0, avail - 1)}…` : rawLine]
|
|
572
|
+
: wrapLine(rawLine, avail);
|
|
573
|
+
for (const segment of segments) {
|
|
574
|
+
this.writeLine(`${prefix}${this.paint(segment, code)}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Stream reasoning according to `showReasoning`: `off` hides it, `summary`
|
|
580
|
+
* shows a bounded preview, `full` shows everything (all dimmed).
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* Stream reasoning according to `showReasoning`: `off` hides it, `summary`
|
|
584
|
+
* shows a bounded preview, `full` shows everything (all dimmed). Returns
|
|
585
|
+
* whether anything was made visible (so the caller can keep a spinner when it
|
|
586
|
+
* is hidden).
|
|
587
|
+
*/
|
|
588
|
+
writeReasoning(event, chunk) {
|
|
589
|
+
if (this.reasoningMode === 'off') {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const region = `${event.frameKind ?? 'plain'}@${event.depth ?? 0}`;
|
|
593
|
+
if (region !== this.reasoningRegion) {
|
|
594
|
+
this.reasoningRegion = region;
|
|
595
|
+
this.reasoningChars = 0;
|
|
596
|
+
this.reasoningTruncated = false;
|
|
597
|
+
}
|
|
598
|
+
const limit = this.reasoningMode === 'summary' ? REASONING_SUMMARY_CHARS : Infinity;
|
|
599
|
+
if (this.reasoningChars >= limit) {
|
|
600
|
+
this.announceReasoningTruncated(event);
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
const remaining = limit - this.reasoningChars;
|
|
604
|
+
const text = chunk.length > remaining ? chunk.slice(0, remaining) : chunk;
|
|
605
|
+
this.reasoningChars += text.length;
|
|
606
|
+
this.writeStream(event, this.paint(text, ANSI.thinking), 'reasoning');
|
|
607
|
+
if (this.reasoningChars >= limit) {
|
|
608
|
+
this.announceReasoningTruncated(event);
|
|
609
|
+
}
|
|
610
|
+
return text.length > 0;
|
|
611
|
+
}
|
|
612
|
+
/** Keep a `thinking…` spinner running (restart once if the label differs). */
|
|
613
|
+
ensureThinkingSpinner(prefix) {
|
|
614
|
+
if (this.spinnerTimer && this.spinnerLabel === 'thinking…') {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.startSpinner(prefix, 'thinking…');
|
|
618
|
+
}
|
|
619
|
+
announceReasoningTruncated(event) {
|
|
620
|
+
if (this.reasoningTruncated) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
this.reasoningTruncated = true;
|
|
624
|
+
this.writeStream(event, this.paint(' … (reasoning truncated; set showReasoning=full to see it all)', ANSI.dim), 'reasoning');
|
|
625
|
+
}
|
|
626
|
+
/** Write a streamed chunk, prefixing it with its frame label on first use. */
|
|
627
|
+
writeStream(event, chunk, kind) {
|
|
628
|
+
if (kind === 'text' && this.markdownEnabled) {
|
|
629
|
+
this.pendingTextEvent = event;
|
|
630
|
+
for (const line of this.markdown.push(chunk)) {
|
|
631
|
+
this.writeStreamChunk(event, `${line}\n`, kind);
|
|
632
|
+
}
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
this.writeStreamChunk(event, chunk, kind);
|
|
636
|
+
}
|
|
637
|
+
writeStreamChunk(event, chunk, kind) {
|
|
638
|
+
// Include the kind so switching reasoning <-> text starts a fresh region.
|
|
639
|
+
const key = `${event.frameKind ?? 'plain'}@${event.depth ?? 0}:${kind}`;
|
|
640
|
+
if (key !== this.lastStreamFrame) {
|
|
641
|
+
if (!this.atLineStart) {
|
|
642
|
+
this.sink('\n');
|
|
643
|
+
}
|
|
644
|
+
const prefix = this.frameLabel(event);
|
|
645
|
+
if (prefix)
|
|
646
|
+
this.sink(prefix);
|
|
647
|
+
this.lastStreamFrame = key;
|
|
648
|
+
this.atLineStart = false;
|
|
649
|
+
}
|
|
650
|
+
this.sink(chunk);
|
|
651
|
+
this.atLineStart = chunk.endsWith('\n');
|
|
652
|
+
}
|
|
653
|
+
/** Close an open streamed-text region before writing a line. */
|
|
654
|
+
flushStream() {
|
|
655
|
+
if (this.markdownEnabled && this.pendingTextEvent) {
|
|
656
|
+
const event = this.pendingTextEvent;
|
|
657
|
+
this.pendingTextEvent = undefined;
|
|
658
|
+
for (const line of this.markdown.flush()) {
|
|
659
|
+
this.writeStreamChunk(event, `${line}\n`, 'text');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (this.lastStreamFrame !== null) {
|
|
663
|
+
if (!this.atLineStart)
|
|
664
|
+
this.sink('\n');
|
|
665
|
+
this.lastStreamFrame = null;
|
|
666
|
+
this.atLineStart = true;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/** `run │ ` for an object frame; nested (`execute` child) events use indentation + `│`. */
|
|
670
|
+
frameLabel(event) {
|
|
671
|
+
// `--no-events` also drops the frame label, so output/answer lines are plain.
|
|
672
|
+
if (!this.showEvents || !event.frameKind)
|
|
673
|
+
return '';
|
|
674
|
+
const depth = event.depth ?? 0;
|
|
675
|
+
// Nested (`execute` child) activity reads as belonging to the parent tool
|
|
676
|
+
// call through indentation plus a hanging `│`, with no `nested` label.
|
|
677
|
+
if (event.frameKind === 'nested') {
|
|
678
|
+
return `${' '.repeat(depth + 1)}│ `;
|
|
679
|
+
}
|
|
680
|
+
const label = event.frameKind === 'object' ? 'run' : event.frameKind;
|
|
681
|
+
const indent = ' '.repeat(depth);
|
|
682
|
+
return `${indent}${label} │ `;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Render one solution's bindings. `terminator` is appended after the last
|
|
686
|
+
* binding line ('.' for the final answer, ' ' while more may follow).
|
|
687
|
+
*/
|
|
688
|
+
solution(bindings, terminator, prefix = '', maxValue) {
|
|
689
|
+
if (this.mode === 'json') {
|
|
690
|
+
this.sink(`${JSON.stringify({ type: 'solution', solution: { bindings, deterministic: false } })}\n`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
this.flushStream();
|
|
694
|
+
if (bindings.length === 0) {
|
|
695
|
+
this.writeLine(`${prefix}true${terminator ?? '.'}`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const lines = bindings.map((binding) => {
|
|
699
|
+
const value = maxValue !== undefined ? truncate(binding.value, maxValue) : binding.value;
|
|
700
|
+
const name = this.paint(binding.name, ANSI.cyan);
|
|
701
|
+
return `${prefix}${name} = ${value}`;
|
|
702
|
+
});
|
|
703
|
+
const last = lines.length - 1;
|
|
704
|
+
lines.forEach((line, index) => {
|
|
705
|
+
this.writeLine(index === last ? `${line}${terminator ?? '.'}` : line);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
paint(text, code) {
|
|
709
|
+
if (!this.color) {
|
|
710
|
+
return text;
|
|
711
|
+
}
|
|
712
|
+
return `${code}${text}${ANSI.reset}`;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
//# sourceMappingURL=render.js.map
|