micro-models-agent 0.63.0 → 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.
Files changed (186) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/cli/cache-line.js +30 -0
  3. package/dist/cli/command-suggest.js +38 -0
  4. package/dist/cli/commands.js +285 -60
  5. package/dist/cli/completer.js +16 -16
  6. package/dist/cli/json-payload.js +32 -0
  7. package/dist/cli/main.js +165 -77
  8. package/dist/cli/plugin-commands.js +5 -4
  9. package/dist/cli/relaunch.js +37 -0
  10. package/dist/cli/repl-commands.js +441 -307
  11. package/dist/cli/repl.js +360 -83
  12. package/dist/cli/run-result.js +12 -6
  13. package/dist/cli/security-commands.js +64 -60
  14. package/dist/cli/setup-order.js +57 -0
  15. package/dist/cli/setup-prompt.js +49 -0
  16. package/dist/cli/setup.js +52 -48
  17. package/dist/config/budget.js +48 -0
  18. package/dist/config/config.js +132 -70
  19. package/dist/config/defaults.js +37 -11
  20. package/dist/config/domains.js +9 -50
  21. package/dist/config/utils.js +56 -0
  22. package/dist/core/agent/audit-gate.js +49 -0
  23. package/dist/core/agent/compaction.js +89 -0
  24. package/dist/core/agent/constants.js +61 -0
  25. package/dist/core/agent/context-renderer.js +40 -0
  26. package/dist/core/agent/hallucination-gate.js +87 -0
  27. package/dist/core/agent/loop-state.js +53 -0
  28. package/dist/core/agent/prefix-monitor.js +101 -0
  29. package/dist/core/agent/reasoning-resolver.js +56 -0
  30. package/dist/core/agent/token-tracker.js +96 -0
  31. package/dist/core/agent/tool-batch.js +237 -0
  32. package/dist/core/agent/tool-output.js +62 -0
  33. package/dist/core/agent-moe.js +214 -69
  34. package/dist/core/agent.js +506 -546
  35. package/dist/core/bootstrap.js +297 -98
  36. package/dist/core/crash-handler.js +2 -1
  37. package/dist/core/prompt-builder.js +3 -0
  38. package/dist/core/prompt-overflow.js +307 -0
  39. package/dist/core/session-logger.js +34 -2
  40. package/dist/i18n/en.json +8 -4
  41. package/dist/i18n/ru.json +8 -4
  42. package/dist/index.js +5 -1
  43. package/dist/llm/cache-usage.js +76 -0
  44. package/dist/llm/image-utils.js +20 -16
  45. package/dist/llm/llm-errors.js +41 -0
  46. package/dist/llm/model-loader.js +30 -0
  47. package/dist/llm/openai-compat.js +287 -101
  48. package/dist/llm/orchestrator.js +140 -68
  49. package/dist/llm/provider-budget.js +68 -0
  50. package/dist/llm/provider.js +0 -1
  51. package/dist/llm/stream-state.js +26 -0
  52. package/dist/llm/token-counter.js +28 -0
  53. package/dist/logger/app-logger.js +12 -15
  54. package/dist/main.js +1755 -841
  55. package/dist/migration/detect.js +3 -1
  56. package/dist/modules/browser/actions.js +0 -3
  57. package/dist/modules/browser/bridge-client.js +2 -0
  58. package/dist/modules/browser/bridge-server.mjs +37 -4
  59. package/dist/modules/browser/driver.js +46 -4
  60. package/dist/modules/certification/cli.js +85 -42
  61. package/dist/modules/certification/loader.js +15 -1
  62. package/dist/modules/certification/manifest.js +126 -15
  63. package/dist/modules/certification/runner.js +4 -26
  64. package/dist/modules/certification/scenarios.js +184 -5
  65. package/dist/modules/certification/syntax-scenarios.js +51 -0
  66. package/dist/modules/context/chunk-query.js +25 -5
  67. package/dist/modules/context/fact-extractor.js +6 -2
  68. package/dist/modules/context/manager.js +23 -7
  69. package/dist/modules/execution/audit-runners.js +7 -1
  70. package/dist/modules/execution/auditor.js +3 -3
  71. package/dist/modules/execution/execution-plugin.js +22 -15
  72. package/dist/modules/execution/input-from.js +46 -0
  73. package/dist/modules/execution/module.js +107 -18
  74. package/dist/modules/execution/moe-executor.js +166 -54
  75. package/dist/modules/execution/plan-actions.js +524 -0
  76. package/dist/modules/execution/plan-steps.js +23 -0
  77. package/dist/modules/execution/plan-store.js +15 -3
  78. package/dist/modules/execution/plan-tool.js +6 -488
  79. package/dist/modules/execution/plan-validator.js +24 -0
  80. package/dist/modules/execution/stuck-detector.js +3 -18
  81. package/dist/modules/execution/tracker.js +14 -5
  82. package/dist/modules/execution/transient-error.js +30 -0
  83. package/dist/modules/execution/verifier.js +94 -7
  84. package/dist/modules/execution/windows-commands.js +11 -0
  85. package/dist/modules/hallucination/confidence.js +36 -23
  86. package/dist/modules/hallucination/consistency.js +3 -0
  87. package/dist/modules/hallucination/detector.js +8 -3
  88. package/dist/modules/hallucination/factual.js +26 -7
  89. package/dist/modules/hallucination/llm-judge.js +12 -2
  90. package/dist/modules/indexer/map-command.js +35 -0
  91. package/dist/modules/indexer/map-select.js +87 -0
  92. package/dist/modules/indexer/module.js +34 -22
  93. package/dist/modules/indexer/symbols.js +189 -0
  94. package/dist/modules/indexer/walker.js +96 -42
  95. package/dist/modules/lsp/check-tool.js +2 -1
  96. package/dist/modules/lsp/client.js +49 -32
  97. package/dist/modules/lsp/config.js +55 -2
  98. package/dist/modules/lsp/module.js +38 -5
  99. package/dist/modules/lsp/probe.js +4 -3
  100. package/dist/modules/lsp/project-root.js +41 -1
  101. package/dist/modules/lsp/startup-check.js +12 -4
  102. package/dist/modules/mcp/client.js +153 -104
  103. package/dist/modules/mcp/module.js +165 -41
  104. package/dist/modules/memory/module.js +4 -3
  105. package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
  106. package/dist/modules/plugins/manager.js +47 -84
  107. package/dist/modules/pricing/index.js +17 -7
  108. package/dist/modules/pricing/prices.js +30 -12
  109. package/dist/modules/processes/index.js +1 -0
  110. package/dist/modules/processes/kill-tree.js +56 -0
  111. package/dist/modules/processes/registry.js +2 -54
  112. package/dist/modules/providers/cache.js +23 -0
  113. package/dist/modules/providers/factory.js +28 -0
  114. package/dist/modules/providers/fallback.js +7 -5
  115. package/dist/modules/providers/health.js +2 -1
  116. package/dist/modules/providers/index.js +1 -0
  117. package/dist/modules/providers/manager.js +17 -2
  118. package/dist/modules/providers/presets.js +79 -6
  119. package/dist/modules/reasoning/policy.js +40 -0
  120. package/dist/modules/reasoning/probe.js +111 -0
  121. package/dist/modules/security/audit-notifier.js +42 -27
  122. package/dist/modules/security/command-validator.js +25 -20
  123. package/dist/modules/security/encryption.js +6 -12
  124. package/dist/modules/security/network-validator.js +76 -5
  125. package/dist/modules/security/path-validator.js +77 -34
  126. package/dist/modules/security/rate-limiter.js +11 -0
  127. package/dist/modules/security/security-policies.js +1 -1
  128. package/dist/modules/security/session-encryption.js +13 -2
  129. package/dist/modules/security/session-isolation.js +2 -9
  130. package/dist/modules/session/manager.js +11 -0
  131. package/dist/modules/session/module.js +11 -3
  132. package/dist/modules/session/store.js +41 -5
  133. package/dist/modules/skills/loader.js +7 -1
  134. package/dist/modules/skills/module.js +2 -1
  135. package/dist/modules/updater/changelog-reader.js +94 -0
  136. package/dist/modules/updater/dev-detect.js +17 -0
  137. package/dist/modules/updater/index.js +1 -0
  138. package/dist/modules/updater/module.js +14 -3
  139. package/dist/output/bus.js +32 -0
  140. package/dist/output/channel.js +233 -0
  141. package/dist/output/format.js +14 -0
  142. package/dist/output/index.js +7 -0
  143. package/dist/output/json-sink.js +22 -0
  144. package/dist/output/machine.js +8 -0
  145. package/dist/output/session-sink.js +27 -0
  146. package/dist/output/types.js +1 -0
  147. package/dist/tools/approve.js +6 -2
  148. package/dist/tools/attach-image.js +11 -11
  149. package/dist/tools/auto-fixer.js +198 -0
  150. package/dist/tools/bash.js +142 -89
  151. package/dist/tools/chunk-query.js +10 -6
  152. package/dist/tools/download-file.js +1 -1
  153. package/dist/tools/edit-file.js +20 -2
  154. package/dist/tools/executor.js +54 -9
  155. package/dist/tools/glob-tool.js +7 -0
  156. package/dist/tools/grep-tool.js +15 -1
  157. package/dist/tools/index.js +3 -1
  158. package/dist/tools/list-dir.js +3 -1
  159. package/dist/tools/load-skill.js +2 -1
  160. package/dist/tools/mcp-call.js +1 -1
  161. package/dist/tools/move-file.js +5 -4
  162. package/dist/tools/path-utils.js +7 -0
  163. package/dist/tools/pipeline-run.js +1 -1
  164. package/dist/tools/prompt-io.js +28 -0
  165. package/dist/tools/question.js +12 -12
  166. package/dist/tools/scope-request.js +91 -0
  167. package/dist/tools/session-info.js +44 -0
  168. package/dist/tools/set-thinking.js +71 -0
  169. package/dist/tools/subagent.js +50 -9
  170. package/dist/tools/syntax-validator.js +177 -0
  171. package/dist/tools/user-input.js +16 -9
  172. package/dist/tools/write-file.js +17 -1
  173. package/dist/ui/diff.js +10 -0
  174. package/dist/ui/line-editor.js +179 -26
  175. package/dist/ui/line-math.js +20 -3
  176. package/dist/ui/md-formatter.js +100 -10
  177. package/dist/ui/output.js +5 -4
  178. package/dist/ui/plan-view.js +2 -7
  179. package/dist/ui/renderer.js +89 -85
  180. package/dist/ui/spinner.js +14 -4
  181. package/dist/utils/error.js +4 -0
  182. package/dist/utils/index.js +4 -0
  183. package/dist/utils/retry.js +17 -0
  184. package/dist/utils/sleep.js +23 -0
  185. package/dist/utils/truncate.js +9 -0
  186. package/package.json +1 -1
@@ -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
- /** Tools that show a busy spinner while running (inline mode). */
34
- const BUSY_TOOLS = new Set(["lsp_check"]);
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
- * and boxed tool cards; plain mode falls back to simple prefixed lines.
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
- out;
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
- stream: this.err,
106
+ channel: this.channel,
104
107
  width: this.width,
105
108
  });
106
- this.fmt = new FormattingStream((line) => this.out.write(`${line}\n`), this.width);
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.card) {
118
- if (this.toolStyle === "inline") {
119
- this.writeInlineBody(chunk);
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.out.write(chunk);
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.out.write(`${GUTTER}${line}\n`);
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.out.write(pc.dim(chunk));
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 — show the thinking spinner. */
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; clear the thinking spinner. */
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
- if (!this.rich) {
160
- this.out.write(`\n${pc.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}${step}\n`);
161
- return;
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.card = { tool, args, body: [], start: Date.now() };
164
- if (this.toolStyle === "inline") {
165
- this.out.write(`\n${pc.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}${step}\n`);
166
- if (BUSY_TOOLS.has(tool)) {
167
- this.spinner.start(t("ui.tool_running", { tool: friendlyTool(tool) }));
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.out.write(`${line}\n`);
210
+ this.channel.writeRaw(`${line}\n`);
179
211
  }
180
212
  }
181
- toolEnd(_tool, duration, error, ctxDelta, costUsd, provider, model) {
213
+ toolEnd(_tool, duration, error, ctxDelta, costUsd) {
182
214
  this.spinner.stop();
183
- const prov = provider && model ? `${pc.dim(`${provider}·${model}`)}` : undefined;
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
- if (prov) {
212
- footer += ` ${pc.dim("via")} ${prov}`;
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.out.write(text);
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.err.write(`${pc.red(text)}\n`);
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)
@@ -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.stream = opts.stream ?? process.stderr;
24
- this.enabled = opts.enabled ?? isSpinnerSupported(this.stream);
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
- this.stream.write("\r\x1b[K");
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
- this.stream.write("\r" + pc.cyan(frame) + " " + this.message + "\x1b[K");
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,4 @@
1
+ /** Extract a human-readable message from an unknown thrown value. */
2
+ export function errMsg(e) {
3
+ return e instanceof Error ? e.message : String(e);
4
+ }
@@ -0,0 +1,4 @@
1
+ export { sleep } from "./sleep";
2
+ export { truncate } from "./truncate";
3
+ export { backoffDelay } from "./retry";
4
+ export { errMsg } from "./error";
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Обрезка строки с многоточием. Раньше жила приватной в context/manager.ts
3
+ * и переписывалась инлайном в openai-compat и mcp/client.
4
+ */
5
+ export function truncate(s, max) {
6
+ if (s.length <= max)
7
+ return s;
8
+ return s.slice(0, max - 3) + "...";
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.63.0",
3
+ "version": "1.1.0",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {