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/renderer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { pc } from "./colors";
|
|
2
2
|
import { FormattingStream } from "./md-formatter";
|
|
3
3
|
import { Spinner } from "./spinner";
|
|
4
|
-
import { box, divider } from "./box";
|
|
5
4
|
import { getTerminalWidth } from "./table";
|
|
6
5
|
import { t } from "../i18n/index";
|
|
7
6
|
import { formatCost } from "../modules/pricing/prices";
|
|
7
|
+
import { getDefaultChannel } from "../output";
|
|
8
8
|
import { isAbsolute, relative, sep } from "path";
|
|
9
9
|
function formatUsd(cost) {
|
|
10
10
|
return formatCost(cost);
|
|
@@ -30,8 +30,9 @@ function toDisplayPath(baseDir, p) {
|
|
|
30
30
|
return rel.split(sep).join("/");
|
|
31
31
|
}
|
|
32
32
|
const GUTTER = " ";
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const MAX_OUTPUT_LINES = 50;
|
|
34
|
+
/** Tools that must NOT get a busy spinner — they block on a user prompt. */
|
|
35
|
+
const SPINNERLESS_TOOLS = new Set(["question", "approve"]);
|
|
35
36
|
/** opencode-like leading marker per tool category. */
|
|
36
37
|
export function toolMarker(tool) {
|
|
37
38
|
switch (tool) {
|
|
@@ -77,53 +78,65 @@ function friendlyTool(tool) {
|
|
|
77
78
|
return value !== key ? value : tool;
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
80
|
-
* Streams agent events to the terminal. Rich mode (TTY) adds a spinner
|
|
81
|
-
*
|
|
81
|
+
* Streams agent events to the terminal. Rich mode (TTY) adds a spinner;
|
|
82
|
+
* plain mode falls back to simple prefixed lines.
|
|
82
83
|
* Colors come from `colors.ts`, disabled outside a TTY, in CI, or on NO_COLOR.
|
|
83
84
|
*/
|
|
84
85
|
export class Renderer {
|
|
85
86
|
rich;
|
|
86
87
|
spinner;
|
|
87
88
|
fmt;
|
|
88
|
-
|
|
89
|
-
err;
|
|
89
|
+
channel;
|
|
90
90
|
width;
|
|
91
|
-
toolStyle;
|
|
92
91
|
baseDir;
|
|
93
92
|
card = null;
|
|
93
|
+
thoughtStarted = false;
|
|
94
|
+
thoughtStartMs = 0;
|
|
95
|
+
thoughtHeaderPrinted = false;
|
|
96
|
+
textStarted = false;
|
|
97
|
+
outputLineCount = 0;
|
|
98
|
+
outputSuppressed = false;
|
|
94
99
|
constructor(opts = {}) {
|
|
95
100
|
this.rich = opts.rich ?? isRichTerminal();
|
|
96
|
-
this.out = opts.out ?? process.stdout;
|
|
97
|
-
this.err = opts.err ?? process.stderr;
|
|
98
101
|
this.width = opts.width ?? getTerminalWidth();
|
|
99
|
-
this.toolStyle = opts.toolStyle ?? "inline";
|
|
100
102
|
this.baseDir = opts.baseDir;
|
|
103
|
+
this.channel = opts.channel ?? getDefaultChannel();
|
|
101
104
|
this.spinner = new Spinner({
|
|
102
105
|
enabled: this.rich && (opts.spinner ?? true),
|
|
103
|
-
|
|
106
|
+
channel: this.channel,
|
|
104
107
|
width: this.width,
|
|
105
108
|
});
|
|
106
|
-
this.fmt = new FormattingStream((line) => this.
|
|
109
|
+
this.fmt = new FormattingStream((line) => this.channel.writeRaw(`${line}\n`), this.width, (text) => this.channel.writeRaw(text), { reservedCols: 2, streamPartials: this.rich });
|
|
110
|
+
}
|
|
111
|
+
/** Show a thinking spinner while waiting for LLM response. */
|
|
112
|
+
showLoader() {
|
|
113
|
+
this.spinner.start(t("ui.thinking"));
|
|
107
114
|
}
|
|
108
115
|
/** Final answer streamed from the model (markdown-formatted). */
|
|
109
116
|
text(chunk) {
|
|
110
117
|
this.endCard();
|
|
111
118
|
this.spinner.stop();
|
|
119
|
+
if (!this.textStarted) {
|
|
120
|
+
this.textStarted = true;
|
|
121
|
+
this.channel.writeRaw(`\n${pc.dim("-")} `);
|
|
122
|
+
}
|
|
112
123
|
this.fmt.write(chunk);
|
|
113
124
|
}
|
|
114
125
|
/** Arbitrary status/meta text (tool output, reasoning, MoE status). */
|
|
115
126
|
meta(chunk) {
|
|
116
127
|
this.spinner.stop();
|
|
117
|
-
if (this.
|
|
118
|
-
if (this.
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
else {
|
|
122
|
-
this.card.body.push(chunk);
|
|
128
|
+
if (this.thoughtStarted) {
|
|
129
|
+
if (!this.thoughtHeaderPrinted) {
|
|
130
|
+
this.channel.writeRaw(`\n${pc.dim("→")} Thought: `);
|
|
131
|
+
this.thoughtHeaderPrinted = true;
|
|
123
132
|
}
|
|
133
|
+
this.channel.writeRaw(pc.dim(chunk));
|
|
134
|
+
}
|
|
135
|
+
else if (this.card) {
|
|
136
|
+
this.writeInlineBody(chunk);
|
|
124
137
|
}
|
|
125
138
|
else {
|
|
126
|
-
this.
|
|
139
|
+
this.channel.writeRaw(chunk);
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
/** Stream tool body lines with the gutter prefix, skipping blank lines. */
|
|
@@ -131,76 +144,79 @@ export class Renderer {
|
|
|
131
144
|
for (const line of chunk.split("\n")) {
|
|
132
145
|
if (line.trim() === "")
|
|
133
146
|
continue;
|
|
134
|
-
this.
|
|
147
|
+
this.outputLineCount++;
|
|
148
|
+
if (this.outputLineCount > MAX_OUTPUT_LINES) {
|
|
149
|
+
this.outputSuppressed = true;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
this.channel.writeRaw(`${GUTTER}${line}\n`);
|
|
135
153
|
}
|
|
136
154
|
}
|
|
137
155
|
/** Dimmed reasoning stream (already colored by the agent). */
|
|
138
156
|
reasoning(chunk) {
|
|
139
157
|
this.spinner.stop();
|
|
140
|
-
this.
|
|
158
|
+
if (this.thoughtStarted && !this.thoughtHeaderPrinted) {
|
|
159
|
+
this.channel.writeRaw(`\n${pc.dim("→")} Thought: `);
|
|
160
|
+
this.thoughtHeaderPrinted = true;
|
|
161
|
+
}
|
|
162
|
+
this.channel.writeRaw(pc.dim(chunk));
|
|
141
163
|
}
|
|
142
|
-
/** Model is loading / generating —
|
|
164
|
+
/** Model is loading / generating — record start time. */
|
|
143
165
|
thinkingStart() {
|
|
166
|
+
this.thoughtStarted = true;
|
|
167
|
+
this.thoughtStartMs = Date.now();
|
|
168
|
+
this.thoughtHeaderPrinted = false;
|
|
144
169
|
this.spinner.start(t("ui.thinking"));
|
|
145
170
|
}
|
|
146
|
-
/** LLM call finished;
|
|
171
|
+
/** LLM call finished; print duration and close the thought block. */
|
|
147
172
|
thinkingEnd() {
|
|
148
173
|
this.spinner.stop();
|
|
174
|
+
if (this.thoughtStarted && this.thoughtHeaderPrinted) {
|
|
175
|
+
const duration = Date.now() - this.thoughtStartMs;
|
|
176
|
+
this.channel.writeRaw(pc.dim(` ${duration}ms\n`));
|
|
177
|
+
}
|
|
178
|
+
this.thoughtStarted = false;
|
|
179
|
+
this.thoughtHeaderPrinted = false;
|
|
149
180
|
}
|
|
150
181
|
toolStart(tool, args, stepContext, icon) {
|
|
151
|
-
this.endCard();
|
|
152
182
|
this.spinner.stop();
|
|
183
|
+
this.fmt.flushPartialNow();
|
|
184
|
+
this.endCard();
|
|
185
|
+
this.outputLineCount = 0;
|
|
186
|
+
this.outputSuppressed = false;
|
|
153
187
|
const displayArgs = PATH_TOOLS.has(tool) && typeof args.path === "string"
|
|
154
188
|
? { ...args, path: toDisplayPath(this.baseDir, args.path) }
|
|
155
189
|
: args;
|
|
156
190
|
const summary = summarizeArgs(displayArgs);
|
|
157
|
-
const step = stepContext ? ` ${pc.cyan(`← ${stepContext}`)}` : "";
|
|
158
191
|
const marker = icon || toolMarker(tool);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
192
|
+
const label = friendlyTool(tool);
|
|
193
|
+
this.card = { tool, args, start: Date.now() };
|
|
194
|
+
if (stepContext) {
|
|
195
|
+
this.channel.writeRaw(`\n${pc.dim("↓")} ${pc.cyan(stepContext)}\n`);
|
|
162
196
|
}
|
|
163
|
-
this.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
return;
|
|
197
|
+
this.channel.writeRaw(`${pc.dim(marker)} ${label}${summary ? ` ${pc.dim(summary)}` : ""}\n`);
|
|
198
|
+
// Busy indicator while the tool actually runs — read/write/edit/bash can
|
|
199
|
+
// take seconds; without this the chat looks frozen until the footer.
|
|
200
|
+
if (!SPINNERLESS_TOOLS.has(tool)) {
|
|
201
|
+
this.spinner.start(`${label}${summary ? ` ${summary}` : ""}`);
|
|
170
202
|
}
|
|
171
|
-
this.spinner.start(`${pc.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}${step}`);
|
|
172
203
|
}
|
|
173
204
|
/** Live plan checklist — prints the plan block as-is (already colored). */
|
|
174
205
|
planBlock(lines) {
|
|
175
|
-
this.endCard();
|
|
176
206
|
this.spinner.stop();
|
|
207
|
+
this.fmt.flushPartialNow();
|
|
208
|
+
this.endCard();
|
|
177
209
|
for (const line of lines) {
|
|
178
|
-
this.
|
|
210
|
+
this.channel.writeRaw(`${line}\n`);
|
|
179
211
|
}
|
|
180
212
|
}
|
|
181
|
-
toolEnd(_tool, duration, error, ctxDelta, costUsd
|
|
213
|
+
toolEnd(_tool, duration, error, ctxDelta, costUsd) {
|
|
182
214
|
this.spinner.stop();
|
|
183
|
-
|
|
184
|
-
if (!this.rich) {
|
|
185
|
-
const parts = [];
|
|
186
|
-
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
187
|
-
const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
|
|
188
|
-
parts.push(`${pc.dim("ctx")} ${deltaStr}`);
|
|
189
|
-
}
|
|
190
|
-
if (costUsd !== undefined && costUsd > 0) {
|
|
191
|
-
parts.push(`${pc.dim("cost")} ${pc.yellow(formatUsd(costUsd))}`);
|
|
192
|
-
}
|
|
193
|
-
if (prov)
|
|
194
|
-
parts.push(prov);
|
|
195
|
-
if (parts.length > 0)
|
|
196
|
-
this.out.write(`${parts.join(" ")}\n`);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
215
|
+
this.fmt.flushPartialNow();
|
|
199
216
|
if (!this.card)
|
|
200
217
|
return;
|
|
201
|
-
const { tool, args, body } = this.card;
|
|
202
218
|
const marker = error ? pc.red("✗") : pc.green("✓");
|
|
203
|
-
let footer = `${marker} ${pc.dim(`${duration}ms`)}`;
|
|
219
|
+
let footer = `${GUTTER}${marker} ${pc.dim(`${duration}ms`)}`;
|
|
204
220
|
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
205
221
|
const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
|
|
206
222
|
footer += ` ${pc.dim("ctx")} ${deltaStr}`;
|
|
@@ -208,47 +224,35 @@ export class Renderer {
|
|
|
208
224
|
if (costUsd !== undefined && costUsd > 0) {
|
|
209
225
|
footer += ` ${pc.dim("cost")} ${pc.yellow(formatUsd(costUsd))}`;
|
|
210
226
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (this.toolStyle === "inline") {
|
|
215
|
-
this.out.write(`${GUTTER}${footer}\n`);
|
|
216
|
-
this.out.write(`${divider(this.width)}\n`);
|
|
217
|
-
this.card = null;
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const lines = [];
|
|
221
|
-
const summary = summarizeArgs(args);
|
|
222
|
-
if (summary)
|
|
223
|
-
lines.push(pc.dim(summary));
|
|
224
|
-
for (const chunk of body) {
|
|
225
|
-
for (const line of chunk.split("\n")) {
|
|
226
|
-
if (line.trim() !== "")
|
|
227
|
-
lines.push(line);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
lines.push(footer);
|
|
231
|
-
const title = `${marker} ${friendlyTool(tool)}`;
|
|
232
|
-
for (const line of box(lines, { title, width: this.width })) {
|
|
233
|
-
this.out.write(`${line}\n`);
|
|
227
|
+
this.channel.writeRaw(`${footer}\n`);
|
|
228
|
+
if (this.outputSuppressed) {
|
|
229
|
+
this.channel.writeRaw(`${GUTTER}${pc.dim(`... (${this.outputLineCount - MAX_OUTPUT_LINES} more lines)`)}\n`);
|
|
234
230
|
}
|
|
235
231
|
this.card = null;
|
|
236
232
|
}
|
|
237
233
|
/** Raw pre-formatted text (diff blocks, warnings). */
|
|
238
234
|
raw(text) {
|
|
239
|
-
this.endCard();
|
|
240
235
|
this.spinner.stop();
|
|
241
|
-
this.
|
|
236
|
+
this.fmt.flushPartialNow();
|
|
237
|
+
this.endCard();
|
|
238
|
+
this.channel.writeRaw(text);
|
|
242
239
|
}
|
|
243
240
|
error(text) {
|
|
244
|
-
this.endCard();
|
|
245
241
|
this.spinner.stop();
|
|
246
|
-
this.
|
|
242
|
+
this.fmt.flushPartialNow();
|
|
243
|
+
this.endCard();
|
|
244
|
+
this.channel.writeRaw(`${pc.red(text)}\n`, "stderr");
|
|
247
245
|
}
|
|
248
246
|
flush() {
|
|
249
247
|
this.endCard();
|
|
250
248
|
this.spinner.stop();
|
|
249
|
+
this.fmt.flushPartialNow();
|
|
251
250
|
this.fmt.flush();
|
|
251
|
+
this.textStarted = false;
|
|
252
|
+
}
|
|
253
|
+
/** Footer with model, provider, and total duration. */
|
|
254
|
+
footer(model, provider, durationMs) {
|
|
255
|
+
this.channel.writeRaw(`\n${pc.dim("▣")} ${model} · ${provider} · ${pc.dim(`${durationMs}ms`)}\n`);
|
|
252
256
|
}
|
|
253
257
|
endCard() {
|
|
254
258
|
if (!this.card)
|
package/dist/ui/spinner.js
CHANGED
|
@@ -16,12 +16,14 @@ export class Spinner {
|
|
|
16
16
|
frame = 0;
|
|
17
17
|
message = "";
|
|
18
18
|
enabled;
|
|
19
|
+
channel;
|
|
19
20
|
stream;
|
|
20
21
|
intervalMs;
|
|
21
22
|
width;
|
|
22
23
|
constructor(opts = {}) {
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
24
|
+
this.channel = opts.channel;
|
|
25
|
+
this.stream = process.stderr;
|
|
26
|
+
this.enabled = opts.enabled ?? isSpinnerSupported();
|
|
25
27
|
this.intervalMs = opts.intervalMs ?? 80;
|
|
26
28
|
this.width = opts.width ?? getTerminalWidth();
|
|
27
29
|
}
|
|
@@ -46,7 +48,11 @@ export class Spinner {
|
|
|
46
48
|
if (this.timer) {
|
|
47
49
|
clearInterval(this.timer);
|
|
48
50
|
this.timer = null;
|
|
49
|
-
|
|
51
|
+
const clear = "\r\x1b[K";
|
|
52
|
+
if (this.channel)
|
|
53
|
+
this.channel.writeOverlay(clear);
|
|
54
|
+
else
|
|
55
|
+
this.stream.write(clear);
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
truncate(text) {
|
|
@@ -65,6 +71,10 @@ export class Spinner {
|
|
|
65
71
|
return;
|
|
66
72
|
const frame = FRAMES[this.frame % FRAMES.length];
|
|
67
73
|
this.frame++;
|
|
68
|
-
|
|
74
|
+
const text = "\r" + pc.cyan(frame) + " " + this.message + "\x1b[K";
|
|
75
|
+
if (this.channel)
|
|
76
|
+
this.channel.writeOverlay(text);
|
|
77
|
+
else
|
|
78
|
+
this.stream.write(text);
|
|
69
79
|
}
|
|
70
80
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Экспоненциальная задержка повтора с опциональным джиттером.
|
|
3
|
+
*
|
|
4
|
+
* Единая реализация вместо четырёх расходящихся копий:
|
|
5
|
+
* - openai-compat (стрим и fetchWithRetry): `min(base*2^attempt, max)` + jitter;
|
|
6
|
+
* - moe-executor: `min(1000*2^(attempt-1), 8000)` без jitter;
|
|
7
|
+
* - audit-notifier: `2^retries * 1000` без потолка.
|
|
8
|
+
*
|
|
9
|
+
* `maxDelay === undefined` — без потолка (копия audit-notifier).
|
|
10
|
+
* `jitterFactor === 0` — без джиттера (копии moe/audit-notifier).
|
|
11
|
+
*/
|
|
12
|
+
export function backoffDelay(attempt, baseDelay, maxDelay, jitterFactor = 0) {
|
|
13
|
+
const base = maxDelay !== undefined
|
|
14
|
+
? Math.min(baseDelay * Math.pow(2, attempt), maxDelay)
|
|
15
|
+
: baseDelay * Math.pow(2, attempt);
|
|
16
|
+
return base + Math.random() * baseDelay * jitterFactor;
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Сон с поддержкой отмены. При abort сигнала отклоняет DOMException
|
|
3
|
+
* "AbortError" — тот же контракт, что у fetch/AbortController, чтобы
|
|
4
|
+
* вызывающий код обрабатывал прерывание единообразно.
|
|
5
|
+
*/
|
|
6
|
+
export function sleep(ms, signal) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
if (signal?.aborted) {
|
|
9
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let timer;
|
|
13
|
+
const onAbort = () => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
16
|
+
};
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
signal?.removeEventListener("abort", onAbort);
|
|
19
|
+
resolve();
|
|
20
|
+
}, ms);
|
|
21
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
22
|
+
});
|
|
23
|
+
}
|