micro-models-agent 0.63.3 → 1.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/CHANGELOG.md +148 -1
- package/dist/cli/cache-line.js +30 -0
- package/dist/cli/command-suggest.js +38 -0
- package/dist/cli/commands.js +285 -60
- package/dist/cli/completer.js +16 -16
- package/dist/cli/json-payload.js +32 -0
- package/dist/cli/main.js +165 -77
- package/dist/cli/plugin-commands.js +5 -4
- package/dist/cli/relaunch.js +37 -0
- package/dist/cli/repl-commands.js +441 -307
- package/dist/cli/repl.js +360 -83
- package/dist/cli/run-result.js +12 -6
- package/dist/cli/security-commands.js +64 -60
- package/dist/cli/setup-order.js +57 -0
- package/dist/cli/setup-prompt.js +49 -0
- package/dist/cli/setup.js +52 -48
- package/dist/config/budget.js +48 -0
- package/dist/config/config.js +132 -70
- package/dist/config/defaults.js +37 -11
- package/dist/config/domains.js +9 -50
- package/dist/config/utils.js +56 -0
- package/dist/core/agent/audit-gate.js +49 -0
- package/dist/core/agent/compaction.js +89 -0
- package/dist/core/agent/constants.js +61 -0
- package/dist/core/agent/context-renderer.js +40 -0
- package/dist/core/agent/hallucination-gate.js +87 -0
- package/dist/core/agent/loop-state.js +53 -0
- package/dist/core/agent/prefix-monitor.js +101 -0
- package/dist/core/agent/reasoning-resolver.js +56 -0
- package/dist/core/agent/token-tracker.js +96 -0
- package/dist/core/agent/tool-batch.js +237 -0
- package/dist/core/agent/tool-output.js +62 -0
- package/dist/core/agent-moe.js +214 -69
- package/dist/core/agent.js +506 -546
- package/dist/core/bootstrap.js +297 -98
- package/dist/core/crash-handler.js +2 -1
- package/dist/core/prompt-builder.js +3 -0
- package/dist/core/prompt-overflow.js +307 -0
- package/dist/core/session-logger.js +34 -2
- package/dist/i18n/en.json +7 -4
- package/dist/i18n/ru.json +7 -4
- package/dist/index.js +5 -1
- package/dist/llm/cache-usage.js +76 -0
- package/dist/llm/image-utils.js +20 -16
- package/dist/llm/llm-errors.js +41 -0
- package/dist/llm/model-loader.js +30 -0
- package/dist/llm/openai-compat.js +287 -101
- package/dist/llm/orchestrator.js +140 -68
- package/dist/llm/provider-budget.js +68 -0
- package/dist/llm/provider.js +0 -1
- package/dist/llm/stream-state.js +26 -0
- package/dist/llm/token-counter.js +28 -0
- package/dist/logger/app-logger.js +12 -15
- package/dist/main.js +1606 -800
- package/dist/migration/detect.js +3 -1
- package/dist/modules/browser/actions.js +0 -3
- package/dist/modules/browser/bridge-client.js +2 -0
- package/dist/modules/browser/driver.js +46 -4
- package/dist/modules/certification/cli.js +85 -42
- package/dist/modules/certification/loader.js +15 -1
- package/dist/modules/certification/manifest.js +126 -15
- package/dist/modules/certification/runner.js +4 -26
- package/dist/modules/certification/scenarios.js +184 -5
- package/dist/modules/certification/syntax-scenarios.js +51 -0
- package/dist/modules/context/chunk-query.js +25 -5
- package/dist/modules/context/fact-extractor.js +6 -2
- package/dist/modules/context/manager.js +23 -7
- package/dist/modules/execution/audit-runners.js +7 -1
- package/dist/modules/execution/auditor.js +3 -3
- package/dist/modules/execution/execution-plugin.js +22 -15
- package/dist/modules/execution/input-from.js +46 -0
- package/dist/modules/execution/module.js +107 -18
- package/dist/modules/execution/moe-executor.js +166 -54
- package/dist/modules/execution/plan-actions.js +524 -0
- package/dist/modules/execution/plan-steps.js +23 -0
- package/dist/modules/execution/plan-store.js +15 -3
- package/dist/modules/execution/plan-tool.js +6 -488
- package/dist/modules/execution/plan-validator.js +24 -0
- package/dist/modules/execution/stuck-detector.js +3 -18
- package/dist/modules/execution/tracker.js +14 -5
- package/dist/modules/execution/transient-error.js +30 -0
- package/dist/modules/execution/verifier.js +94 -7
- package/dist/modules/execution/windows-commands.js +11 -0
- package/dist/modules/hallucination/confidence.js +36 -23
- package/dist/modules/hallucination/consistency.js +3 -0
- package/dist/modules/hallucination/detector.js +8 -3
- package/dist/modules/hallucination/factual.js +26 -7
- package/dist/modules/hallucination/llm-judge.js +12 -2
- package/dist/modules/indexer/map-command.js +35 -0
- package/dist/modules/indexer/map-select.js +87 -0
- package/dist/modules/indexer/module.js +34 -22
- package/dist/modules/indexer/symbols.js +189 -0
- package/dist/modules/indexer/walker.js +96 -42
- package/dist/modules/lsp/check-tool.js +2 -1
- package/dist/modules/lsp/client.js +49 -32
- package/dist/modules/lsp/config.js +55 -2
- package/dist/modules/lsp/module.js +38 -5
- package/dist/modules/lsp/probe.js +4 -3
- package/dist/modules/lsp/project-root.js +41 -1
- package/dist/modules/lsp/startup-check.js +12 -4
- package/dist/modules/mcp/client.js +153 -104
- package/dist/modules/mcp/module.js +165 -41
- package/dist/modules/memory/module.js +4 -3
- package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
- package/dist/modules/plugins/manager.js +47 -84
- package/dist/modules/pricing/index.js +17 -7
- package/dist/modules/pricing/prices.js +30 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +56 -0
- package/dist/modules/processes/registry.js +2 -54
- package/dist/modules/providers/cache.js +23 -0
- package/dist/modules/providers/factory.js +28 -0
- package/dist/modules/providers/fallback.js +7 -5
- package/dist/modules/providers/health.js +2 -1
- package/dist/modules/providers/index.js +1 -0
- package/dist/modules/providers/manager.js +17 -2
- package/dist/modules/providers/presets.js +79 -6
- package/dist/modules/reasoning/policy.js +40 -0
- package/dist/modules/reasoning/probe.js +111 -0
- package/dist/modules/security/audit-notifier.js +42 -27
- package/dist/modules/security/command-validator.js +25 -20
- package/dist/modules/security/encryption.js +6 -12
- package/dist/modules/security/network-validator.js +76 -5
- package/dist/modules/security/path-validator.js +77 -34
- package/dist/modules/security/rate-limiter.js +11 -0
- package/dist/modules/security/security-policies.js +1 -1
- package/dist/modules/security/session-encryption.js +13 -2
- package/dist/modules/security/session-isolation.js +2 -9
- package/dist/modules/session/manager.js +11 -0
- package/dist/modules/session/module.js +11 -3
- package/dist/modules/session/store.js +41 -5
- package/dist/modules/skills/loader.js +7 -1
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/changelog-reader.js +94 -0
- package/dist/modules/updater/dev-detect.js +17 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +14 -3
- package/dist/output/bus.js +32 -0
- package/dist/output/channel.js +233 -0
- package/dist/output/format.js +14 -0
- package/dist/output/index.js +7 -0
- package/dist/output/json-sink.js +22 -0
- package/dist/output/machine.js +8 -0
- package/dist/output/session-sink.js +27 -0
- package/dist/output/types.js +1 -0
- package/dist/tools/approve.js +6 -2
- package/dist/tools/attach-image.js +11 -11
- package/dist/tools/auto-fixer.js +198 -0
- package/dist/tools/bash.js +142 -89
- package/dist/tools/chunk-query.js +10 -6
- package/dist/tools/download-file.js +1 -1
- package/dist/tools/edit-file.js +20 -2
- package/dist/tools/executor.js +54 -9
- package/dist/tools/glob-tool.js +7 -0
- package/dist/tools/grep-tool.js +15 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/list-dir.js +3 -1
- package/dist/tools/load-skill.js +2 -1
- package/dist/tools/mcp-call.js +1 -1
- package/dist/tools/move-file.js +5 -4
- package/dist/tools/path-utils.js +7 -0
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/prompt-io.js +28 -0
- package/dist/tools/question.js +12 -12
- package/dist/tools/scope-request.js +91 -0
- package/dist/tools/session-info.js +44 -0
- package/dist/tools/set-thinking.js +71 -0
- package/dist/tools/subagent.js +50 -9
- package/dist/tools/syntax-validator.js +177 -0
- package/dist/tools/user-input.js +16 -9
- package/dist/tools/write-file.js +17 -1
- package/dist/ui/diff.js +10 -0
- package/dist/ui/line-editor.js +179 -26
- package/dist/ui/line-math.js +20 -3
- package/dist/ui/md-formatter.js +100 -10
- package/dist/ui/output.js +5 -4
- package/dist/ui/plan-view.js +2 -7
- package/dist/ui/renderer.js +89 -85
- package/dist/ui/spinner.js +14 -4
- package/dist/utils/error.js +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/retry.js +17 -0
- package/dist/utils/sleep.js +23 -0
- package/dist/utils/truncate.js +9 -0
- package/package.json +1 -1
package/dist/ui/line-editor.js
CHANGED
|
@@ -29,6 +29,12 @@ export class LineEditor {
|
|
|
29
29
|
questionCb = null;
|
|
30
30
|
rawMode = false;
|
|
31
31
|
listeners = {};
|
|
32
|
+
/** stdin handlers registered in the constructor — removed in close() so
|
|
33
|
+
* re-creating the editor never accumulates stale listeners. */
|
|
34
|
+
dsrDataHandler;
|
|
35
|
+
keypressHandler;
|
|
36
|
+
dataHandler;
|
|
37
|
+
endHandler;
|
|
32
38
|
/** Visual row (within the drawn frame) where the cursor was placed by the
|
|
33
39
|
* last render — fallback for moving back to the frame's first row before
|
|
34
40
|
* redrawing when no absolute anchor is known yet. Relative moves break
|
|
@@ -46,6 +52,18 @@ export class LineEditor {
|
|
|
46
52
|
dsrPending = false;
|
|
47
53
|
/** Buffer for partial DSR escape sequences split across data chunks. */
|
|
48
54
|
dsrTail = "";
|
|
55
|
+
/** Visual row of the cursor at the exact moment the outstanding DSR query
|
|
56
|
+
* was sent. The reply is matched against this, not `prevCursorRow`, which
|
|
57
|
+
* later renders overwrite while the query is still pending. */
|
|
58
|
+
dsrQueryRow = 0;
|
|
59
|
+
/** When false, keystrokes are ignored (except Ctrl+C) while the agent
|
|
60
|
+
* streams — the host owns the terminal during that phase. */
|
|
61
|
+
inputEnabled = true;
|
|
62
|
+
/** While true the prompt frame is not drawn; the agent owns the screen. */
|
|
63
|
+
frameSuspended = false;
|
|
64
|
+
/** Clears `dsrPending` when no DSR reply arrives, falling back to
|
|
65
|
+
* relative cursor moves instead of wedging on a broken terminal. */
|
|
66
|
+
dsrTimer = null;
|
|
49
67
|
constructor(opts) {
|
|
50
68
|
this.input = opts.input;
|
|
51
69
|
this.output = opts.output;
|
|
@@ -65,13 +83,19 @@ export class LineEditor {
|
|
|
65
83
|
// Raw data listener for DSR cursor-position replies. Runs alongside
|
|
66
84
|
// readline's keypress emitter (both receive every chunk); replies may
|
|
67
85
|
// arrive merged with regular keystrokes, hence the global regex.
|
|
68
|
-
this.
|
|
86
|
+
this.dsrDataHandler = (buf) => this.consumeDsrReplies(buf.toString("utf-8"));
|
|
87
|
+
this.input.on("data", this.dsrDataHandler);
|
|
69
88
|
readline.emitKeypressEvents(this.input);
|
|
70
|
-
this.
|
|
89
|
+
this.keypressHandler = (str, key) => this.onKey(str, key);
|
|
90
|
+
this.input.on("keypress", this.keypressHandler);
|
|
91
|
+
// The editor owns stdin now: ensure it is flowing even if a previous
|
|
92
|
+
// consumer paused it (e.g. the setup wizard).
|
|
93
|
+
this.input.resume?.();
|
|
71
94
|
}
|
|
72
95
|
else {
|
|
73
|
-
this.
|
|
74
|
-
this.input.on("
|
|
96
|
+
this.dataHandler = (buf) => this.onData(buf);
|
|
97
|
+
this.input.on("data", this.dataHandler);
|
|
98
|
+
this.endHandler = () => {
|
|
75
99
|
const last = this.lines.join("\n");
|
|
76
100
|
this.lines = [""];
|
|
77
101
|
this.row = 0;
|
|
@@ -79,7 +103,8 @@ export class LineEditor {
|
|
|
79
103
|
if (last)
|
|
80
104
|
this.emitLine(last);
|
|
81
105
|
this.emitClose();
|
|
82
|
-
}
|
|
106
|
+
};
|
|
107
|
+
this.input.on("end", this.endHandler);
|
|
83
108
|
}
|
|
84
109
|
}
|
|
85
110
|
on(event, listener) {
|
|
@@ -103,6 +128,61 @@ export class LineEditor {
|
|
|
103
128
|
write(s) {
|
|
104
129
|
this.insertText(s);
|
|
105
130
|
}
|
|
131
|
+
setInputEnabled(enabled) {
|
|
132
|
+
this.inputEnabled = enabled;
|
|
133
|
+
}
|
|
134
|
+
isInputEnabled() {
|
|
135
|
+
return this.inputEnabled;
|
|
136
|
+
}
|
|
137
|
+
isFrameSuspended() {
|
|
138
|
+
return this.frameSuspended;
|
|
139
|
+
}
|
|
140
|
+
/** Clear the prompt frame (the agent takes over the terminal) and stop
|
|
141
|
+
* rendering until `resumeFrame()`. */
|
|
142
|
+
suspendFrame() {
|
|
143
|
+
if (!this.input.isTTY || this.isDone)
|
|
144
|
+
return;
|
|
145
|
+
const out = [];
|
|
146
|
+
if (this.frameTopAbs !== null)
|
|
147
|
+
out.push(`\x1b[${this.frameTopAbs};1H`);
|
|
148
|
+
else if (this.prevCursorRow > 0)
|
|
149
|
+
out.push(`\x1b[${this.prevCursorRow}A`);
|
|
150
|
+
out.push("\r\x1b[J");
|
|
151
|
+
this.output.write(out.join(""));
|
|
152
|
+
this.invalidateAnchor();
|
|
153
|
+
this.frameSuspended = true;
|
|
154
|
+
}
|
|
155
|
+
/** Restore the frame after streaming. */
|
|
156
|
+
resumeFrame() {
|
|
157
|
+
this.frameSuspended = false;
|
|
158
|
+
this.invalidateAnchor();
|
|
159
|
+
this.render();
|
|
160
|
+
}
|
|
161
|
+
/** Print a block of lines above the current prompt frame without erasing
|
|
162
|
+
* the input buffer. When the frame is suspended the block is printed in
|
|
163
|
+
* place and the suspension is preserved. */
|
|
164
|
+
printAbove(text) {
|
|
165
|
+
if (!this.input.isTTY || this.isDone) {
|
|
166
|
+
this.output.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const wasSuspended = this.frameSuspended;
|
|
170
|
+
this.frameSuspended = false;
|
|
171
|
+
const out = [];
|
|
172
|
+
if (this.frameTopAbs !== null)
|
|
173
|
+
out.push(`\x1b[${this.frameTopAbs};1H`);
|
|
174
|
+
else if (this.prevCursorRow > 0)
|
|
175
|
+
out.push(`\x1b[${this.prevCursorRow}A`);
|
|
176
|
+
out.push("\r\x1b[J");
|
|
177
|
+
out.push(text.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n").join("\r\n"));
|
|
178
|
+
out.push("\r\n");
|
|
179
|
+
this.output.write(out.join(""));
|
|
180
|
+
this.invalidateAnchor();
|
|
181
|
+
if (wasSuspended)
|
|
182
|
+
this.frameSuspended = true;
|
|
183
|
+
else
|
|
184
|
+
this.render();
|
|
185
|
+
}
|
|
106
186
|
reset() {
|
|
107
187
|
this.lines = [""];
|
|
108
188
|
this.row = 0;
|
|
@@ -128,9 +208,22 @@ export class LineEditor {
|
|
|
128
208
|
return;
|
|
129
209
|
this.isDone = true;
|
|
130
210
|
this.disableTerminalProtocols();
|
|
211
|
+
const inp = this.input;
|
|
212
|
+
if (this.dsrDataHandler)
|
|
213
|
+
inp.removeListener("data", this.dsrDataHandler);
|
|
214
|
+
if (this.keypressHandler)
|
|
215
|
+
inp.removeListener("keypress", this.keypressHandler);
|
|
216
|
+
if (this.dataHandler)
|
|
217
|
+
inp.removeListener("data", this.dataHandler);
|
|
218
|
+
if (this.endHandler)
|
|
219
|
+
inp.removeListener("end", this.endHandler);
|
|
131
220
|
if (this.rawMode && this.input.setRawMode) {
|
|
132
221
|
this.input.setRawMode(false);
|
|
133
222
|
}
|
|
223
|
+
if (this.dsrTimer) {
|
|
224
|
+
clearTimeout(this.dsrTimer);
|
|
225
|
+
this.dsrTimer = null;
|
|
226
|
+
}
|
|
134
227
|
this.emitClose();
|
|
135
228
|
}
|
|
136
229
|
// ---- input handling ----
|
|
@@ -158,6 +251,20 @@ export class LineEditor {
|
|
|
158
251
|
onKey(str, key) {
|
|
159
252
|
if (this.isDone)
|
|
160
253
|
return;
|
|
254
|
+
if (!this.inputEnabled) {
|
|
255
|
+
// While streaming, only interrupt keys are honored: Ctrl+C aborts the
|
|
256
|
+
// run, Escape is forwarded so the host can implement double-Esc abort.
|
|
257
|
+
const k = key;
|
|
258
|
+
if (k?.ctrl && k.name === "c") {
|
|
259
|
+
this.ctrlC();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (k?.name === "escape") {
|
|
263
|
+
this.onKeyInput?.(str, key);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
161
268
|
if (this.onKeyInput && this.onKeyInput(str, key))
|
|
162
269
|
return;
|
|
163
270
|
const k = key;
|
|
@@ -170,6 +277,12 @@ export class LineEditor {
|
|
|
170
277
|
}
|
|
171
278
|
return;
|
|
172
279
|
}
|
|
280
|
+
// Never treat a cursor-position report (`ESC[row;colR`) as typed input:
|
|
281
|
+
// on some Windows/ConPTY + Bun combinations the trailing "R" leaks into
|
|
282
|
+
// the buffer and corrupts the answer (observed as every prompt answering
|
|
283
|
+
// "R"). Swallow the whole sequence.
|
|
284
|
+
if (/^\x1b\[\d+;\d+R$/.test(seq))
|
|
285
|
+
return;
|
|
173
286
|
if (name === "paste-start" || seq === "\x1b[200~") {
|
|
174
287
|
this.isPaste = true;
|
|
175
288
|
this.pasteBuffer = "";
|
|
@@ -665,7 +778,18 @@ export class LineEditor {
|
|
|
665
778
|
return;
|
|
666
779
|
}
|
|
667
780
|
if (matches.length === 1) {
|
|
668
|
-
|
|
781
|
+
// Complete the partial under the CURSOR, not the last occurrence in
|
|
782
|
+
// the whole line — with the cursor mid-line, lastIndexOf() inserted
|
|
783
|
+
// at the line's end instead.
|
|
784
|
+
const chars = Array.from(line);
|
|
785
|
+
const beforeCursor = chars.slice(0, this.col).join("");
|
|
786
|
+
let from = beforeCursor.lastIndexOf(partial);
|
|
787
|
+
if (from === -1)
|
|
788
|
+
from = line.lastIndexOf(partial);
|
|
789
|
+
if (from === -1) {
|
|
790
|
+
this.output.write("\x07");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
669
793
|
const head = line.slice(0, from);
|
|
670
794
|
const tail = line.slice(from + partial.length);
|
|
671
795
|
this.lines[this.row] = head + matches[0] + tail;
|
|
@@ -673,9 +797,13 @@ export class LineEditor {
|
|
|
673
797
|
this.render();
|
|
674
798
|
return;
|
|
675
799
|
}
|
|
676
|
-
const
|
|
677
|
-
this.output.write("\r\n" +
|
|
678
|
-
|
|
800
|
+
const rows = matches.map((m) => " " + m);
|
|
801
|
+
this.output.write("\r\n" + rows.join("\r\n") + "\r\n");
|
|
802
|
+
// The printed list moved the cursor (rows.length + 1) visual rows down
|
|
803
|
+
// from its frame position. Account for those rows so the next render
|
|
804
|
+
// redraws the frame in place — otherwise its relative up-move starts
|
|
805
|
+
// above the frame and paints garbage over the just-printed list.
|
|
806
|
+
this.prevCursorRow += rows.length + 1;
|
|
679
807
|
this.render();
|
|
680
808
|
}
|
|
681
809
|
clearScreen() {
|
|
@@ -696,6 +824,19 @@ export class LineEditor {
|
|
|
696
824
|
const r = this.output.rows;
|
|
697
825
|
return typeof r === "number" && r > 3 ? r : null;
|
|
698
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* Whether the cursor-position query (`ESC[6n`) may be sent. It is opt-in on
|
|
829
|
+
* Windows: ConPTY can mis-decode the `ESC[row;colR` reply as typed input,
|
|
830
|
+
* which corrupts answers (the setup wizard answered every prompt "R").
|
|
831
|
+
* `MMA_DSR=1` forces it on, `MMA_DSR=0` off; elsewhere it defaults on.
|
|
832
|
+
*/
|
|
833
|
+
dsrEnabled() {
|
|
834
|
+
if (process.env.MMA_DSR === "1")
|
|
835
|
+
return true;
|
|
836
|
+
if (process.env.MMA_DSR === "0")
|
|
837
|
+
return false;
|
|
838
|
+
return process.platform !== "win32";
|
|
839
|
+
}
|
|
699
840
|
/** Cap the rendered frame to the visible screen. A frame taller than the
|
|
700
841
|
* terminal cannot be redrawn in place at all (its top scrolls off), so
|
|
701
842
|
* only the tail is drawn; the absolute anchor keeps the next render
|
|
@@ -717,6 +858,10 @@ export class LineEditor {
|
|
|
717
858
|
this.dsrPending = false;
|
|
718
859
|
this.dsrTail = "";
|
|
719
860
|
this.prevCursorRow = 0;
|
|
861
|
+
if (this.dsrTimer) {
|
|
862
|
+
clearTimeout(this.dsrTimer);
|
|
863
|
+
this.dsrTimer = null;
|
|
864
|
+
}
|
|
720
865
|
}
|
|
721
866
|
/** Parse `ESC[row;colR` cursor-position replies. Buffers partial escape
|
|
722
867
|
* sequences across chunks. Accepts at most one reply (the outstanding
|
|
@@ -732,17 +877,20 @@ export class LineEditor {
|
|
|
732
877
|
return;
|
|
733
878
|
this.dsrPending = false;
|
|
734
879
|
const row = Number(m[1]);
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
//
|
|
740
|
-
|
|
741
|
-
|
|
880
|
+
if (this.dsrTimer) {
|
|
881
|
+
clearTimeout(this.dsrTimer);
|
|
882
|
+
this.dsrTimer = null;
|
|
883
|
+
}
|
|
884
|
+
// The frame top is the reply row minus the cursor's visual row at query
|
|
885
|
+
// time. `dsrQueryRow` — not `prevCursorRow` — because intermediate
|
|
886
|
+
// renders may have advanced the cursor while the query was pending.
|
|
887
|
+
const top = row - this.dsrQueryRow;
|
|
888
|
+
const H = this.screenRows();
|
|
889
|
+
if (top >= 1 && (H === null || top <= H))
|
|
742
890
|
this.frameTopAbs = top;
|
|
743
891
|
}
|
|
744
892
|
render() {
|
|
745
|
-
if (!this.input.isTTY || this.isDone)
|
|
893
|
+
if (!this.input.isTTY || this.isDone || this.frameSuspended)
|
|
746
894
|
return;
|
|
747
895
|
const layout = this.visibleLayout();
|
|
748
896
|
const promptWidth = stringWidth(stripAnsi(this.promptStr));
|
|
@@ -780,6 +928,7 @@ export class LineEditor {
|
|
|
780
928
|
if (ccol > 0)
|
|
781
929
|
out.push(`\x1b[${ccol}C`);
|
|
782
930
|
this.output.write(out.join(""));
|
|
931
|
+
this.prevCursorRow = idx;
|
|
783
932
|
// Deterministic scroll math: update the anchor for the NEXT render.
|
|
784
933
|
// After writing L rows starting from frameTopAbs, if the frame extends
|
|
785
934
|
// past the screen bottom the terminal scrolls; the new frame top is
|
|
@@ -794,16 +943,20 @@ export class LineEditor {
|
|
|
794
943
|
// else: no scroll, anchor unchanged
|
|
795
944
|
}
|
|
796
945
|
}
|
|
797
|
-
else if (!this.dsrPending) {
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
946
|
+
else if (!this.dsrPending && this.questionCb === null && this.dsrEnabled()) {
|
|
947
|
+
// One DSR query per frame, then the anchor is maintained by local scroll
|
|
948
|
+
// math. Never queried in question mode (see dsrEnabled). A timeout clears
|
|
949
|
+
// dsrPending so a non-responding terminal falls back to relative moves.
|
|
950
|
+
this.dsrPending = true;
|
|
951
|
+
this.dsrQueryRow = idx;
|
|
952
|
+
this.output.write("\x1b[6n");
|
|
953
|
+
if (this.dsrTimer)
|
|
954
|
+
clearTimeout(this.dsrTimer);
|
|
955
|
+
this.dsrTimer = setTimeout(() => {
|
|
956
|
+
this.dsrPending = false;
|
|
957
|
+
this.dsrTimer = null;
|
|
958
|
+
}, 150);
|
|
805
959
|
}
|
|
806
|
-
this.prevCursorRow = idx;
|
|
807
960
|
}
|
|
808
961
|
enableTerminalProtocols() {
|
|
809
962
|
this.output.write("\x1b[?2004h");
|
package/dist/ui/line-math.js
CHANGED
|
@@ -15,19 +15,36 @@ export function wrapLine(line, width, firstRowWidth) {
|
|
|
15
15
|
const rows = [];
|
|
16
16
|
let start = 0;
|
|
17
17
|
let cells = 0;
|
|
18
|
+
let lastSpaceIdx = -1;
|
|
19
|
+
let lastSpaceCells = 0;
|
|
18
20
|
for (let i = 0; i < chars.length; i++) {
|
|
19
21
|
const ch = chars[i];
|
|
20
22
|
if (ch === "\n") {
|
|
21
23
|
rows.push({ text: chars.slice(start, i).join(""), charStart: start });
|
|
22
24
|
start = i + 1;
|
|
23
25
|
cells = 0;
|
|
26
|
+
lastSpaceIdx = -1;
|
|
27
|
+
lastSpaceCells = 0;
|
|
24
28
|
continue;
|
|
25
29
|
}
|
|
26
30
|
const cw = stringWidth(ch);
|
|
31
|
+
if (ch === " ") {
|
|
32
|
+
lastSpaceIdx = i;
|
|
33
|
+
lastSpaceCells = cells;
|
|
34
|
+
}
|
|
27
35
|
if (cells + cw > limit && cells > 0) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
if (lastSpaceIdx > start) {
|
|
37
|
+
rows.push({ text: chars.slice(start, lastSpaceIdx).join(""), charStart: start });
|
|
38
|
+
start = lastSpaceIdx + 1;
|
|
39
|
+
cells = Math.max(0, cells - lastSpaceCells - 1);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
rows.push({ text: chars.slice(start, i).join(""), charStart: start });
|
|
43
|
+
start = i;
|
|
44
|
+
cells = cw;
|
|
45
|
+
}
|
|
46
|
+
lastSpaceIdx = -1;
|
|
47
|
+
lastSpaceCells = 0;
|
|
31
48
|
limit = w;
|
|
32
49
|
}
|
|
33
50
|
else {
|
package/dist/ui/md-formatter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pc } from "./colors";
|
|
2
|
+
import stringWidth from "string-width";
|
|
2
3
|
import { t } from "../i18n/index";
|
|
3
4
|
import { formatTable, getTerminalWidth } from "./table";
|
|
4
5
|
const KW = /\b(import|export|from|const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|new|class|extends|implements|interface|type|enum|namespace|module|declare|abstract|async|await|yield|throw|try|catch|finally|typeof|instanceof|in|of|as|is|keyof|readonly|static|private|public|protected|get|set|constructor|this|super|void|never|any|unknown|boolean|string|number|symbol|object|true|false|null|undefined|default)\b/g;
|
|
@@ -52,10 +53,25 @@ export class FormattingStream {
|
|
|
52
53
|
codeLines = [];
|
|
53
54
|
tableBuffer = [];
|
|
54
55
|
onWrite;
|
|
56
|
+
onRawWrite;
|
|
55
57
|
width;
|
|
56
|
-
|
|
58
|
+
/** Display columns of the raw partial currently shown on the terminal. */
|
|
59
|
+
partialWritten = 0;
|
|
60
|
+
/** How many chars of the pending line have already been streamed raw. */
|
|
61
|
+
streamedChars = 0;
|
|
62
|
+
/** Columns reserved on the left (caller prefix) so a partial never wraps. */
|
|
63
|
+
reservedCols;
|
|
64
|
+
/**
|
|
65
|
+
* Stream the current (not-yet-newline-terminated) line raw for live
|
|
66
|
+
* feedback. Off by default — offline callers use `formatMarkdown`.
|
|
67
|
+
*/
|
|
68
|
+
streamPartials;
|
|
69
|
+
constructor(onWrite, width, onRawWrite, opts = {}) {
|
|
57
70
|
this.onWrite = onWrite;
|
|
71
|
+
this.onRawWrite = onRawWrite ?? onWrite;
|
|
58
72
|
this.width = width ?? getTerminalWidth();
|
|
73
|
+
this.reservedCols = opts.reservedCols ?? 0;
|
|
74
|
+
this.streamPartials = opts.streamPartials ?? false;
|
|
59
75
|
}
|
|
60
76
|
write(chunk) {
|
|
61
77
|
this.buffer += chunk;
|
|
@@ -63,27 +79,101 @@ export class FormattingStream {
|
|
|
63
79
|
while ((idx = this.buffer.indexOf("\n")) !== -1) {
|
|
64
80
|
const line = this.buffer.slice(0, idx);
|
|
65
81
|
this.buffer = this.buffer.slice(idx + 1);
|
|
82
|
+
this.emitPartialRewind();
|
|
66
83
|
this.processLine(line);
|
|
67
84
|
}
|
|
85
|
+
if (this.buffer.length > 0 && !this.inCodeBlock) {
|
|
86
|
+
this.streamPartial();
|
|
87
|
+
}
|
|
68
88
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Stream the pending line raw for real-time feedback; the formatted line
|
|
91
|
+
* replaces it once the newline arrives.
|
|
92
|
+
*
|
|
93
|
+
* The replacement uses a RELATIVE cursor move (`\x1b[<cols>D`), which is only
|
|
94
|
+
* correct while the streamed text stays on the single visual line it started
|
|
95
|
+
* on. If the partial would exceed the terminal width it wraps and every
|
|
96
|
+
* relative move lands in the wrong place — the garbled output observed after
|
|
97
|
+
* the 1.0.0 output refactor (missing/swapped characters, leaked markers,
|
|
98
|
+
* broken tables). So streaming freezes at the last chunk that fits and the
|
|
99
|
+
* line is rendered when it completes.
|
|
100
|
+
*/
|
|
101
|
+
streamPartial() {
|
|
102
|
+
if (!this.streamPartials)
|
|
103
|
+
return;
|
|
104
|
+
const available = Math.max(1, this.width - this.reservedCols);
|
|
105
|
+
if (stringWidth(this.buffer) > available)
|
|
106
|
+
return;
|
|
107
|
+
const delta = this.buffer.slice(this.streamedChars);
|
|
108
|
+
if (!delta)
|
|
109
|
+
return;
|
|
110
|
+
this.onRawWrite(delta);
|
|
111
|
+
this.streamedChars = this.buffer.length;
|
|
112
|
+
this.partialWritten = stringWidth(this.buffer);
|
|
113
|
+
}
|
|
114
|
+
emitPartialRewind() {
|
|
115
|
+
if (this.partialWritten > 0) {
|
|
116
|
+
// Move back over the streamed partial and erase any tail left by a
|
|
117
|
+
// shorter formatted replacement before the caller renders the line.
|
|
118
|
+
this.onRawWrite(`\x1b[${this.partialWritten}D\x1b[K`);
|
|
119
|
+
this.partialWritten = 0;
|
|
120
|
+
}
|
|
121
|
+
this.streamedChars = 0;
|
|
122
|
+
}
|
|
123
|
+
/** True when there is displayed partial output or unprocessed buffered text. @internal */
|
|
124
|
+
hasPartial() {
|
|
125
|
+
return this.partialWritten > 0 || this.buffer.length > 0 || this.tableBuffer.length > 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Finalize a displayed raw partial so the next write starts on a fresh line.
|
|
129
|
+
* Only the already-streamed partial is terminated (with a single newline via
|
|
130
|
+
* `onRawWrite`); any pending table buffer is flushed. An open code block is
|
|
131
|
+
* intentionally left untouched — closing it here would make the real closing
|
|
132
|
+
* fence look like a new opening fence and corrupt all following prose. The
|
|
133
|
+
* code block is emitted by the final `flush()`. Idempotent.
|
|
134
|
+
*/
|
|
135
|
+
flushPartialNow() {
|
|
136
|
+
if (this.partialWritten > 0) {
|
|
137
|
+
// The line is already on screen raw; terminate it as-is (it may be
|
|
138
|
+
// interrupted mid-markdown) and drop it so it is not rendered twice.
|
|
139
|
+
this.onRawWrite("\n");
|
|
140
|
+
this.partialWritten = 0;
|
|
141
|
+
this.streamedChars = 0;
|
|
142
|
+
this.buffer = "";
|
|
143
|
+
}
|
|
144
|
+
else if (this.buffer.length > 0 && !this.inCodeBlock) {
|
|
145
|
+
// Never streamed (plain mode) — render the pending line now.
|
|
146
|
+
const line = this.buffer;
|
|
147
|
+
this.buffer = "";
|
|
148
|
+
this.streamedChars = 0;
|
|
149
|
+
this.onWrite(this.formatLine(line));
|
|
72
150
|
}
|
|
73
151
|
if (this.tableBuffer.length > 0) {
|
|
74
152
|
this.flushTable();
|
|
75
153
|
}
|
|
154
|
+
}
|
|
155
|
+
flush() {
|
|
156
|
+
if (this.tableBuffer.length > 0) {
|
|
157
|
+
this.flushTable();
|
|
158
|
+
}
|
|
159
|
+
if (this.inCodeBlock) {
|
|
160
|
+
if (this.buffer.length > 0) {
|
|
161
|
+
this.codeLines.push(this.buffer);
|
|
162
|
+
this.buffer = "";
|
|
163
|
+
}
|
|
164
|
+
if (this.codeLines.length > 0) {
|
|
165
|
+
this.emitCodeBlock();
|
|
166
|
+
}
|
|
167
|
+
this.streamedChars = 0;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
76
170
|
if (this.buffer.length > 0) {
|
|
77
171
|
const line = this.buffer;
|
|
78
172
|
this.buffer = "";
|
|
79
|
-
|
|
80
|
-
if (this.codeLines.length > 0) {
|
|
81
|
-
this.emitCodeBlock();
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
173
|
+
this.emitPartialRewind();
|
|
85
174
|
this.onWrite(this.formatLine(line));
|
|
86
175
|
}
|
|
176
|
+
this.streamedChars = 0;
|
|
87
177
|
}
|
|
88
178
|
processLine(line) {
|
|
89
179
|
if (line.trim() === "```" || line.trim().startsWith("```")) {
|
package/dist/ui/output.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { getDefaultChannel } from "../output";
|
|
1
2
|
import { formatMarkdown, formatError, formatSuccess, formatWarning } from "./md-formatter";
|
|
2
3
|
export function writeOutput(text) {
|
|
3
|
-
|
|
4
|
+
getDefaultChannel().writeRaw(formatMarkdown(text) + "\n");
|
|
4
5
|
}
|
|
5
6
|
export function writeError(text) {
|
|
6
|
-
|
|
7
|
+
getDefaultChannel().writeRaw(formatError(text) + "\n", "stderr");
|
|
7
8
|
}
|
|
8
9
|
export function writeSuccess(text) {
|
|
9
|
-
|
|
10
|
+
getDefaultChannel().writeRaw(formatSuccess(text) + "\n");
|
|
10
11
|
}
|
|
11
12
|
export function writeWarning(text) {
|
|
12
|
-
|
|
13
|
+
getDefaultChannel().writeLine(formatWarning(text));
|
|
13
14
|
}
|
package/dist/ui/plan-view.js
CHANGED
|
@@ -28,8 +28,7 @@ export function readActivePlan(baseDir) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
export function currentStepIndex(plan) {
|
|
31
|
-
|
|
32
|
-
return idx === -1 ? 0 : idx;
|
|
31
|
+
return plan.steps.findIndex((s) => s.status !== "done" && s.status !== "skipped");
|
|
33
32
|
}
|
|
34
33
|
function stepMark(step) {
|
|
35
34
|
switch (step.status) {
|
|
@@ -95,9 +94,5 @@ export function stepContextForTool(plan, tool, args) {
|
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
|
-
|
|
99
|
-
const step = plan.steps[cur];
|
|
100
|
-
if (!step)
|
|
101
|
-
return null;
|
|
102
|
-
return t("ui.step_context", { id: step.id, desc: truncate(step.description) });
|
|
97
|
+
return null;
|
|
103
98
|
}
|