micro-models-agent 2.4.0 → 2.5.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 +18 -0
- package/dist/assets/app.js +99 -45
- package/dist/assets/styles.css +23 -5
- package/dist/i18n/en.json +9 -2
- package/dist/i18n/ru.json +8 -1
- package/dist/main.js +176 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.5.0] - 2026-09-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **web file viewer** (`src/modules/webui/file-api.ts`): `GET /api/file?path=<p>` serves a workspace text file for the chat file cards' Open link — resolved against the workspace (lexical + `realpath` guards, so symlinks/junctions cannot escape), protected paths (`.git/`, `.env*`, keys) refused, 512 KB cap, binary refused. UTF-16 LE/BE BOM and UTF-8 BOM are decoded; errors are human-readable `text/plain` (the endpoint opens as a browser tab).
|
|
12
|
+
- **web cookie auth** (`src/modules/webui/util.ts`, `server.ts`): authenticated requests set an `HttpOnly; SameSite=Strict` `mma_web_auth` cookie and `checkAuth` accepts it, so a plain `<a target="_blank">` navigation authenticates without the token in the URL.
|
|
13
|
+
- **web session persistence** (`assets/app.js`): the active session is mirrored into `?session=` (`history.replaceState`, token preserved) and restored on reload after the first state refresh.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **web reasoning text** (`src/modules/webui/forward.ts`, `history-api.ts`): ANSI escape sequences (`ESC[2m…`) are stripped from streamed/replayed text, meta and tool output — reasoning no longer renders as literal `\u001b[2m…` garbage on the model's own words.
|
|
18
|
+
- **web file card** (`assets/app.js`, `styles.css`): the card stacks vertically (path on its own line) and its `open` control is a real `<a target="_blank">` link that opens the file in a new tab.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **dedicated reasoning channel** (`src/core/agent.ts`, `src/modules/webui/forward.ts`, `src/cli/repl.ts`, `src/cli/main.ts`): model reasoning now travels on its own `onReasoning` callback and SSE `reasoning` event (no longer pre-dimmed and no longer mixed into the generic `meta` channel); the terminal renders it via `renderer.reasoning`.
|
|
23
|
+
- **web reasoning collapsed by default** (`assets/app.js`): `thinking` blocks (live and replayed) start collapsed; generic `meta` (status, context stats, compaction, tool summaries, warnings) renders as always-visible `.metaln` lines instead of being folded into the reasoning block.
|
|
24
|
+
|
|
7
25
|
## [2.4.0] - 2026-09-24
|
|
8
26
|
|
|
9
27
|
### Added
|
package/dist/assets/app.js
CHANGED
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const FILE_TOOLS = ["read_file", "write_file", "edit_file", "list_dir", "delete_file", "move_file", "file_info"];
|
|
56
|
+
// Tools whose `path` argument points at a readable file the viewer can open.
|
|
57
|
+
// `list_dir` is a directory, `delete_file`/`move_file` leave the file gone.
|
|
58
|
+
const OPENABLE_TOOLS = ["read_file", "write_file", "edit_file", "file_info"];
|
|
56
59
|
const IMAGE_TOOLS = ["attach_image", "browser"];
|
|
57
60
|
const LINK_TOOLS = ["web_fetch", "download_file"];
|
|
58
61
|
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
|
|
84
87
|
let curText = null;
|
|
85
88
|
let curTextNodes = [];
|
|
86
|
-
let
|
|
89
|
+
let curThink = null;
|
|
87
90
|
let lastPlanSig = null;
|
|
88
91
|
let lastUsageSig = null;
|
|
89
92
|
let lastEnvSig = null;
|
|
@@ -365,6 +368,7 @@
|
|
|
365
368
|
|
|
366
369
|
function showEmptyFeed() {
|
|
367
370
|
msgs.replaceChildren(emptyView());
|
|
371
|
+
syncUrl();
|
|
368
372
|
}
|
|
369
373
|
|
|
370
374
|
function clearFeed(feed) {
|
|
@@ -530,8 +534,8 @@
|
|
|
530
534
|
clearBusy(feed);
|
|
531
535
|
if (!feed) return;
|
|
532
536
|
clearEmpty(feed);
|
|
533
|
-
if (
|
|
534
|
-
|
|
537
|
+
if (curThink) {
|
|
538
|
+
finishThink();
|
|
535
539
|
}
|
|
536
540
|
if (curText == null) {
|
|
537
541
|
flushRun();
|
|
@@ -542,39 +546,60 @@
|
|
|
542
546
|
renderTextSegment(feed, curText);
|
|
543
547
|
}
|
|
544
548
|
|
|
545
|
-
function
|
|
546
|
-
if (
|
|
547
|
-
const dur =
|
|
548
|
-
if (dur) dur.textContent = "· " + ((Date.now() -
|
|
549
|
+
function finishThink() {
|
|
550
|
+
if (curThink && curThink.startedAt) {
|
|
551
|
+
const dur = curThink.head.querySelector(".dur");
|
|
552
|
+
if (dur) dur.textContent = "· " + ((Date.now() - curThink.startedAt) / 1000).toFixed(1) + "s";
|
|
549
553
|
}
|
|
550
|
-
|
|
554
|
+
curThink = null;
|
|
551
555
|
}
|
|
552
556
|
|
|
553
|
-
|
|
557
|
+
// Build the collapsible reasoning block. The global `.think-h` click handler
|
|
558
|
+
// depends on this exact structure: wrap > [head, body], body carries `hidden`.
|
|
559
|
+
function buildThinkBlock(withDur) {
|
|
560
|
+
const wrap = el("div", "think");
|
|
561
|
+
const head = el("button", "think-h");
|
|
562
|
+
head.type = "button";
|
|
563
|
+
head.setAttribute("aria-expanded", "false");
|
|
564
|
+
head.appendChild(el("span", "g", "✦"));
|
|
565
|
+
head.appendChild(el("span", "lab", t("web.action.thinking")));
|
|
566
|
+
if (withDur) head.appendChild(el("span", "dur", ""));
|
|
567
|
+
const tog = el("span", "tog");
|
|
568
|
+
setIcon(tog, "chevron-right", t("web.action.show"));
|
|
569
|
+
head.appendChild(tog);
|
|
570
|
+
const body = el("div", "think-body hidden");
|
|
571
|
+
wrap.appendChild(head);
|
|
572
|
+
wrap.appendChild(body);
|
|
573
|
+
return { wrap, head, body };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function onReasoning(chunk, sid) {
|
|
554
577
|
const feed = feedForEvent(sid);
|
|
555
578
|
clearBusy(feed);
|
|
556
579
|
if (!feed) return;
|
|
557
580
|
clearEmpty(feed);
|
|
558
581
|
curText = null;
|
|
559
|
-
if (!
|
|
582
|
+
if (!curThink) {
|
|
560
583
|
flushRun();
|
|
561
|
-
const wrap =
|
|
562
|
-
const head = el("button", "think-h");
|
|
563
|
-
head.type = "button";
|
|
564
|
-
head.setAttribute("aria-expanded", "true");
|
|
565
|
-
head.appendChild(el("span", "g", "✦"));
|
|
566
|
-
head.appendChild(el("span", "lab", t("web.action.thinking")));
|
|
567
|
-
head.appendChild(el("span", "dur", ""));
|
|
568
|
-
const tog = el("span", "tog");
|
|
569
|
-
setIcon(tog, "chevron-down", t("web.action.hide"));
|
|
570
|
-
head.appendChild(tog);
|
|
571
|
-
const body = el("div", "think-body");
|
|
572
|
-
wrap.appendChild(head);
|
|
573
|
-
wrap.appendChild(body);
|
|
584
|
+
const { wrap, head, body } = buildThinkBlock(true);
|
|
574
585
|
appendTo(feed, wrap);
|
|
575
|
-
|
|
586
|
+
curThink = { wrap, head, body, startedAt: Date.now() };
|
|
576
587
|
}
|
|
577
|
-
|
|
588
|
+
curThink.body.textContent += chunk;
|
|
589
|
+
if (feed === activeFeed()) scroll();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Generic agent status (context stats, compaction, MoE status, warnings):
|
|
593
|
+
// always visible, one dim line per chunk, never collapsed.
|
|
594
|
+
function onMeta(chunk, sid) {
|
|
595
|
+
const feed = feedForEvent(sid);
|
|
596
|
+
clearBusy(feed);
|
|
597
|
+
const text = (chunk || "").trim();
|
|
598
|
+
if (!feed || !text) return;
|
|
599
|
+
clearEmpty(feed);
|
|
600
|
+
curText = null;
|
|
601
|
+
const warn = text.indexOf("⚠") !== -1 || text.indexOf("✗") !== -1;
|
|
602
|
+
appendTo(feed, el("div", warn ? "metaln warn" : "metaln", text));
|
|
578
603
|
if (feed === activeFeed()) scroll();
|
|
579
604
|
}
|
|
580
605
|
|
|
@@ -593,7 +618,7 @@
|
|
|
593
618
|
if (!feed) return;
|
|
594
619
|
clearEmpty(feed);
|
|
595
620
|
curText = null;
|
|
596
|
-
|
|
621
|
+
finishThink();
|
|
597
622
|
pendingArgs.set(data.toolCallId, data.args || {});
|
|
598
623
|
const line = el("div", "act " + categoryOf(data.name));
|
|
599
624
|
line.appendChild(el("span", "g", "◇"));
|
|
@@ -652,10 +677,22 @@
|
|
|
652
677
|
const path = args.path || args.file || args.dir;
|
|
653
678
|
if (!path) return;
|
|
654
679
|
const card = el("div", "filecard");
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
680
|
+
const top = el("div", "fc-top");
|
|
681
|
+
top.appendChild(el("span", "g", "◇"));
|
|
682
|
+
const nameEl = el("span", "name", path);
|
|
683
|
+
nameEl.title = path;
|
|
684
|
+
top.appendChild(nameEl);
|
|
685
|
+
card.appendChild(top);
|
|
686
|
+
const bottom = el("div", "fc-bottom");
|
|
687
|
+
bottom.appendChild(el("span", "lab", labelOf(name)));
|
|
688
|
+
if (OPENABLE_TOOLS.indexOf(name) !== -1) {
|
|
689
|
+
const openLink = el("a", "fc-open", t("web.card.open"));
|
|
690
|
+
openLink.href = "/api/file?path=" + encodeURIComponent(path);
|
|
691
|
+
openLink.target = "_blank";
|
|
692
|
+
openLink.rel = "noopener noreferrer";
|
|
693
|
+
bottom.appendChild(openLink);
|
|
694
|
+
}
|
|
695
|
+
card.appendChild(bottom);
|
|
659
696
|
appendTo(feed, card);
|
|
660
697
|
return;
|
|
661
698
|
}
|
|
@@ -711,7 +748,7 @@
|
|
|
711
748
|
state.busy = false;
|
|
712
749
|
const feed = feedForEvent(data.sessionId);
|
|
713
750
|
clearBusy(feed);
|
|
714
|
-
|
|
751
|
+
finishThink();
|
|
715
752
|
curText = null;
|
|
716
753
|
flushRun();
|
|
717
754
|
const owner = tabs.find((x) => x.id === data.sessionId);
|
|
@@ -782,19 +819,8 @@
|
|
|
782
819
|
}
|
|
783
820
|
|
|
784
821
|
function renderThinkBlock(feed, text) {
|
|
785
|
-
const wrap =
|
|
786
|
-
const head = el("button", "think-h");
|
|
787
|
-
head.type = "button";
|
|
788
|
-
head.setAttribute("aria-expanded", "true");
|
|
789
|
-
head.appendChild(el("span", "g", "✦"));
|
|
790
|
-
head.appendChild(el("span", "lab", t("web.action.thinking")));
|
|
791
|
-
const tog = el("span", "tog");
|
|
792
|
-
setIcon(tog, "chevron-down", t("web.action.hide"));
|
|
793
|
-
head.appendChild(tog);
|
|
794
|
-
const body = el("div", "think-body");
|
|
822
|
+
const { wrap, body } = buildThinkBlock(false);
|
|
795
823
|
body.textContent = text || "";
|
|
796
|
-
wrap.appendChild(head);
|
|
797
|
-
wrap.appendChild(body);
|
|
798
824
|
feed.appendChild(wrap);
|
|
799
825
|
}
|
|
800
826
|
|
|
@@ -867,7 +893,7 @@
|
|
|
867
893
|
}
|
|
868
894
|
|
|
869
895
|
function feedHasContent(feed) {
|
|
870
|
-
return !!feed.querySelector(".u, .ans, .code, .act, .think, .errline, .sysline");
|
|
896
|
+
return !!feed.querySelector(".u, .ans, .code, .act, .think, .metaln, .errline, .sysline");
|
|
871
897
|
}
|
|
872
898
|
|
|
873
899
|
async function loadHistory(tab) {
|
|
@@ -940,6 +966,16 @@
|
|
|
940
966
|
{ passive: false }
|
|
941
967
|
);
|
|
942
968
|
|
|
969
|
+
// Mirror the active session into `?session=` so a page refresh restores it.
|
|
970
|
+
// Only the active tab is persisted; the token param is preserved as-is.
|
|
971
|
+
function syncUrl() {
|
|
972
|
+
const url = new URL(location.href);
|
|
973
|
+
const tab = tabs.find((x) => x.id === activeTabId);
|
|
974
|
+
if (tab && tab.kind === "session") url.searchParams.set("session", tab.id);
|
|
975
|
+
else url.searchParams.delete("session");
|
|
976
|
+
history.replaceState(null, "", url);
|
|
977
|
+
}
|
|
978
|
+
|
|
943
979
|
function openTab(id, name, kind, server) {
|
|
944
980
|
let tab = tabs.find((x) => x.id === id);
|
|
945
981
|
if (!tab) {
|
|
@@ -967,9 +1003,14 @@
|
|
|
967
1003
|
tab.historyLoaded = true;
|
|
968
1004
|
}
|
|
969
1005
|
if (server && tab.kind === "session") {
|
|
1006
|
+
// A tab auto-opened as "live" (server=false) sets historyLoaded=true; when
|
|
1007
|
+
// it is opened from the server afterward we must actually fetch the
|
|
1008
|
+
// transcript, or a reload of the active session shows an empty feed.
|
|
1009
|
+
if (!feedHasContent(tab.feed)) tab.historyLoaded = false;
|
|
970
1010
|
api("/api/sessions/activate", { method: "POST", body: { id } }).then(() => loadSessions());
|
|
971
1011
|
loadHistory(tab);
|
|
972
1012
|
}
|
|
1013
|
+
syncUrl();
|
|
973
1014
|
}
|
|
974
1015
|
|
|
975
1016
|
function closeTab(id) {
|
|
@@ -990,6 +1031,7 @@
|
|
|
990
1031
|
}
|
|
991
1032
|
renderTabstrip();
|
|
992
1033
|
renderSessionsDropdown();
|
|
1034
|
+
syncUrl();
|
|
993
1035
|
}
|
|
994
1036
|
|
|
995
1037
|
async function newChatTab() {
|
|
@@ -1390,7 +1432,7 @@
|
|
|
1390
1432
|
}
|
|
1391
1433
|
clearEmpty(feed);
|
|
1392
1434
|
curText = null;
|
|
1393
|
-
|
|
1435
|
+
finishThink();
|
|
1394
1436
|
if (text.startsWith("/")) {
|
|
1395
1437
|
appendTo(feed, el("div", "u", text));
|
|
1396
1438
|
clearInput();
|
|
@@ -2106,6 +2148,10 @@
|
|
|
2106
2148
|
const d = parse(e);
|
|
2107
2149
|
if (d) onMeta(d.chunk || "", d.sessionId);
|
|
2108
2150
|
});
|
|
2151
|
+
es.addEventListener("reasoning", (e) => {
|
|
2152
|
+
const d = parse(e);
|
|
2153
|
+
if (d) onReasoning(d.chunk || "", d.sessionId);
|
|
2154
|
+
});
|
|
2109
2155
|
es.addEventListener("phase", (e) => {
|
|
2110
2156
|
const d = parse(e);
|
|
2111
2157
|
if (d) onPhase(d.phase, d.sessionId);
|
|
@@ -3027,6 +3073,8 @@
|
|
|
3027
3073
|
}
|
|
3028
3074
|
|
|
3029
3075
|
async function boot() {
|
|
3076
|
+
// Read the requested session before `showEmptyFeed()` → `syncUrl()` can strip it.
|
|
3077
|
+
const initialSession = new URLSearchParams(location.search).get("session");
|
|
3030
3078
|
await loadI18n();
|
|
3031
3079
|
await loadCommands();
|
|
3032
3080
|
applyTheme(currentTheme());
|
|
@@ -3042,6 +3090,12 @@
|
|
|
3042
3090
|
await loadSkills();
|
|
3043
3091
|
await loadPlugins();
|
|
3044
3092
|
await refresh();
|
|
3093
|
+
// Restore the session from the URL AFTER the first refresh: `renderState`
|
|
3094
|
+
// may auto-open the server's active session, and the requested one must win.
|
|
3095
|
+
if (initialSession) {
|
|
3096
|
+
const meta = state.sessions.find((s) => s.id === initialSession);
|
|
3097
|
+
if (meta) openTab(initialSession, meta.name || String(initialSession), "session", true);
|
|
3098
|
+
}
|
|
3045
3099
|
connectSSE();
|
|
3046
3100
|
setInterval(refresh, 2000);
|
|
3047
3101
|
setInterval(loadSessions, 15000);
|
package/dist/assets/styles.css
CHANGED
|
@@ -239,6 +239,11 @@ body {
|
|
|
239
239
|
.think-h .tog:hover { color: var(--dim-a); }
|
|
240
240
|
.think-body { color: var(--dim-c); padding-left: 18px; white-space: pre-wrap; font-size: 12.5px; overflow-wrap: anywhere; }
|
|
241
241
|
.think-body.hidden { display: none; }
|
|
242
|
+
.metaln {
|
|
243
|
+
color: var(--dim-c); padding-left: 26px; white-space: pre-wrap;
|
|
244
|
+
font-size: 12.5px; overflow-wrap: anywhere;
|
|
245
|
+
}
|
|
246
|
+
.metaln.warn { color: var(--yellow); }
|
|
242
247
|
.act { display: flex; gap: 8px; min-width: 0; }
|
|
243
248
|
.act .lab { white-space: nowrap; }
|
|
244
249
|
.act .args { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
@@ -298,14 +303,27 @@ body.light .code { border: 1px solid var(--border); }
|
|
|
298
303
|
body.light .imgcard .ph { background: var(--ph-light); }
|
|
299
304
|
.imgcard .cap { padding: 6px 10px; color: var(--dim-a); font-size: 12px; }
|
|
300
305
|
.filecard {
|
|
301
|
-
margin-left: 26px; padding:
|
|
306
|
+
margin-left: 26px; padding: 8px 12px; border-radius: 6px;
|
|
302
307
|
background: var(--surface); border: 1px solid var(--border-2); color: var(--muted);
|
|
303
|
-
display: flex;
|
|
304
|
-
max-width: 480px; min-width: 0;
|
|
308
|
+
display: flex; flex-direction: column; gap: 6px; align-items: stretch;
|
|
309
|
+
font-size: 12.5px; max-width: 480px; min-width: 0;
|
|
305
310
|
}
|
|
311
|
+
.filecard .fc-top { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
|
|
306
312
|
.filecard .g { color: var(--cyan); flex: 0 0 auto; }
|
|
307
|
-
.filecard .name {
|
|
308
|
-
|
|
313
|
+
.filecard .name {
|
|
314
|
+
color: var(--fg); min-width: 0; overflow-wrap: anywhere; word-break: break-word;
|
|
315
|
+
}
|
|
316
|
+
.filecard .fc-bottom {
|
|
317
|
+
display: flex; gap: 10px; align-items: center; justify-content: space-between;
|
|
318
|
+
}
|
|
319
|
+
.filecard .lab { color: var(--dim-a); min-width: 0; overflow-wrap: anywhere; }
|
|
320
|
+
.filecard .fc-open {
|
|
321
|
+
flex: 0 0 auto; font: inherit; font-size: 12px; line-height: 1.4;
|
|
322
|
+
color: var(--accent); background: transparent; border: 1px solid var(--border-2);
|
|
323
|
+
border-radius: 4px; padding: 2px 10px; cursor: pointer; text-decoration: none;
|
|
324
|
+
}
|
|
325
|
+
.filecard .fc-open:hover { border-color: var(--accent); text-decoration: none; }
|
|
326
|
+
.filecard .fc-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
309
327
|
.linkline { margin-left: 26px; min-width: 0; overflow-wrap: anywhere; }
|
|
310
328
|
.linkline a { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; word-break: break-word; }
|
|
311
329
|
.linkline a:hover { text-decoration: underline; }
|
package/dist/i18n/en.json
CHANGED
|
@@ -1166,7 +1166,7 @@
|
|
|
1166
1166
|
"web.tool.output_few": "output · {count} chars",
|
|
1167
1167
|
"web.tool.output_many": "output · {count} chars",
|
|
1168
1168
|
"web.tool.collapse": "collapse",
|
|
1169
|
-
"web.card.
|
|
1169
|
+
"web.card.open": "open ↗",
|
|
1170
1170
|
"web.card.screenshot": "screenshot",
|
|
1171
1171
|
"web.card.attached_image": "attached image",
|
|
1172
1172
|
"web.card.browser_viewport": "browser · viewport",
|
|
@@ -1174,5 +1174,12 @@
|
|
|
1174
1174
|
"web.question.send": "send",
|
|
1175
1175
|
"web.question.answered": "answered",
|
|
1176
1176
|
"web.question.multi_hint": "select one or more",
|
|
1177
|
-
"web.misc.command_unavailable": "command unavailable"
|
|
1177
|
+
"web.misc.command_unavailable": "command unavailable",
|
|
1178
|
+
"web.file.error.missing_path": "No file path was given.",
|
|
1179
|
+
"web.file.error.outside_workspace": "This file is outside the agent workspace and cannot be opened.",
|
|
1180
|
+
"web.file.error.protected_path": "This file is protected (secrets/VCS) and cannot be opened.",
|
|
1181
|
+
"web.file.error.too_large": "File is too large to open in the browser ({limit} bytes max).",
|
|
1182
|
+
"web.file.error.binary_file": "This looks like a binary file and cannot be shown as text.",
|
|
1183
|
+
"web.file.error.not_found": "File not found (it may be a directory or it was deleted).",
|
|
1184
|
+
"web.file.error.workspace_unavailable": "No workspace is open."
|
|
1178
1185
|
}
|
package/dist/i18n/ru.json
CHANGED
|
@@ -1148,7 +1148,7 @@
|
|
|
1148
1148
|
"web.tool.output_few": "вывод · {count} символа",
|
|
1149
1149
|
"web.tool.output_many": "вывод · {count} символов",
|
|
1150
1150
|
"web.tool.collapse": "свернуть",
|
|
1151
|
-
"web.card.
|
|
1151
|
+
"web.card.open": "открыть ↗",
|
|
1152
1152
|
"web.card.screenshot": "снимок экрана",
|
|
1153
1153
|
"web.card.attached_image": "прикреплённое изображение",
|
|
1154
1154
|
"web.card.browser_viewport": "браузер · область просмотра",
|
|
@@ -1157,6 +1157,13 @@
|
|
|
1157
1157
|
"web.question.answered": "отвечено",
|
|
1158
1158
|
"web.question.multi_hint": "выбери один или несколько",
|
|
1159
1159
|
"web.misc.command_unavailable": "команда недоступна",
|
|
1160
|
+
"web.file.error.missing_path": "Не указан путь к файлу.",
|
|
1161
|
+
"web.file.error.outside_workspace": "Файл вне рабочей папки агента — открыть нельзя.",
|
|
1162
|
+
"web.file.error.protected_path": "Файл защищён (секреты/VCS) — открыть нельзя.",
|
|
1163
|
+
"web.file.error.too_large": "Файл слишком большой для показа в браузере (максимум {limit} байт).",
|
|
1164
|
+
"web.file.error.binary_file": "Похоже на бинарный файл — текстом показать нельзя.",
|
|
1165
|
+
"web.file.error.not_found": "Файл не найден (возможно, это папка или он удалён).",
|
|
1166
|
+
"web.file.error.workspace_unavailable": "Рабочая папка не открыта.",
|
|
1160
1167
|
"config.desc.model": "Модель основного агента.",
|
|
1161
1168
|
"config.desc.provider.active": "Какой из настроенных провайдеров используется.",
|
|
1162
1169
|
"config.desc.contextWindow": "Общий размер контекста в токенах (системный промпт + история + резерв).",
|
package/dist/main.js
CHANGED
|
@@ -3668,7 +3668,7 @@ Updated: {updatedAt}`,
|
|
|
3668
3668
|
"web.tool.output_few": "output · {count} chars",
|
|
3669
3669
|
"web.tool.output_many": "output · {count} chars",
|
|
3670
3670
|
"web.tool.collapse": "collapse",
|
|
3671
|
-
"web.card.
|
|
3671
|
+
"web.card.open": "open ↗",
|
|
3672
3672
|
"web.card.screenshot": "screenshot",
|
|
3673
3673
|
"web.card.attached_image": "attached image",
|
|
3674
3674
|
"web.card.browser_viewport": "browser · viewport",
|
|
@@ -3676,7 +3676,14 @@ Updated: {updatedAt}`,
|
|
|
3676
3676
|
"web.question.send": "send",
|
|
3677
3677
|
"web.question.answered": "answered",
|
|
3678
3678
|
"web.question.multi_hint": "select one or more",
|
|
3679
|
-
"web.misc.command_unavailable": "command unavailable"
|
|
3679
|
+
"web.misc.command_unavailable": "command unavailable",
|
|
3680
|
+
"web.file.error.missing_path": "No file path was given.",
|
|
3681
|
+
"web.file.error.outside_workspace": "This file is outside the agent workspace and cannot be opened.",
|
|
3682
|
+
"web.file.error.protected_path": "This file is protected (secrets/VCS) and cannot be opened.",
|
|
3683
|
+
"web.file.error.too_large": "File is too large to open in the browser ({limit} bytes max).",
|
|
3684
|
+
"web.file.error.binary_file": "This looks like a binary file and cannot be shown as text.",
|
|
3685
|
+
"web.file.error.not_found": "File not found (it may be a directory or it was deleted).",
|
|
3686
|
+
"web.file.error.workspace_unavailable": "No workspace is open."
|
|
3680
3687
|
};
|
|
3681
3688
|
});
|
|
3682
3689
|
|
|
@@ -4899,7 +4906,7 @@ var init_ru = __esm(() => {
|
|
|
4899
4906
|
"web.tool.output_few": "вывод · {count} символа",
|
|
4900
4907
|
"web.tool.output_many": "вывод · {count} символов",
|
|
4901
4908
|
"web.tool.collapse": "свернуть",
|
|
4902
|
-
"web.card.
|
|
4909
|
+
"web.card.open": "открыть ↗",
|
|
4903
4910
|
"web.card.screenshot": "снимок экрана",
|
|
4904
4911
|
"web.card.attached_image": "прикреплённое изображение",
|
|
4905
4912
|
"web.card.browser_viewport": "браузер · область просмотра",
|
|
@@ -4908,6 +4915,13 @@ var init_ru = __esm(() => {
|
|
|
4908
4915
|
"web.question.answered": "отвечено",
|
|
4909
4916
|
"web.question.multi_hint": "выбери один или несколько",
|
|
4910
4917
|
"web.misc.command_unavailable": "команда недоступна",
|
|
4918
|
+
"web.file.error.missing_path": "Не указан путь к файлу.",
|
|
4919
|
+
"web.file.error.outside_workspace": "Файл вне рабочей папки агента — открыть нельзя.",
|
|
4920
|
+
"web.file.error.protected_path": "Файл защищён (секреты/VCS) — открыть нельзя.",
|
|
4921
|
+
"web.file.error.too_large": "Файл слишком большой для показа в браузере (максимум {limit} байт).",
|
|
4922
|
+
"web.file.error.binary_file": "Похоже на бинарный файл — текстом показать нельзя.",
|
|
4923
|
+
"web.file.error.not_found": "Файл не найден (возможно, это папка или он удалён).",
|
|
4924
|
+
"web.file.error.workspace_unavailable": "Рабочая папка не открыта.",
|
|
4911
4925
|
"config.desc.model": "Модель основного агента.",
|
|
4912
4926
|
"config.desc.provider.active": "Какой из настроенных провайдеров используется.",
|
|
4913
4927
|
"config.desc.contextWindow": "Общий размер контекста в токенах (системный промпт + история + резерв).",
|
|
@@ -17044,7 +17058,7 @@ class Agent {
|
|
|
17044
17058
|
}, phase);
|
|
17045
17059
|
onPhase?.(phase);
|
|
17046
17060
|
}
|
|
17047
|
-
async run(input, onChunk, onMeta, onTool, onPhase) {
|
|
17061
|
+
async run(input, onChunk, onMeta, onTool, onPhase, onReasoning) {
|
|
17048
17062
|
this.shutdownRequested = false;
|
|
17049
17063
|
this.abortController = new AbortController;
|
|
17050
17064
|
this.setScope();
|
|
@@ -17130,14 +17144,14 @@ class Agent {
|
|
|
17130
17144
|
toolExecutor,
|
|
17131
17145
|
logger,
|
|
17132
17146
|
baseDir: this.deps.baseDir
|
|
17133
|
-
}, input, () => this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase), {
|
|
17147
|
+
}, input, () => this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning), {
|
|
17134
17148
|
onMeta,
|
|
17135
17149
|
onTool: countTool,
|
|
17136
17150
|
onPhase,
|
|
17137
17151
|
signal: this.abortController.signal,
|
|
17138
17152
|
onEvent: (event) => slog.logMoE(event.type, event.data),
|
|
17139
17153
|
sessionId: this.deps.sessionManager?.getActiveMeta()?.id
|
|
17140
|
-
}) : await this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase);
|
|
17154
|
+
}) : await this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning);
|
|
17141
17155
|
if (moeEnabled && result.moeHandled) {
|
|
17142
17156
|
if (result.text) {
|
|
17143
17157
|
contextManager.addMessage({ role: "assistant", content: result.text });
|
|
@@ -17161,7 +17175,7 @@ class Agent {
|
|
|
17161
17175
|
throw err;
|
|
17162
17176
|
}
|
|
17163
17177
|
}
|
|
17164
|
-
async executeSingleAgentLoop(input, slog, onChunk, onMeta, onTool, onPhase) {
|
|
17178
|
+
async executeSingleAgentLoop(input, slog, onChunk, onMeta, onTool, onPhase, onReasoning) {
|
|
17165
17179
|
const {
|
|
17166
17180
|
config,
|
|
17167
17181
|
llmProvider,
|
|
@@ -17284,7 +17298,7 @@ class Agent {
|
|
|
17284
17298
|
break;
|
|
17285
17299
|
if (chunk.type === "text" && chunk.content) {
|
|
17286
17300
|
if (emittedReasoning && !textContent) {
|
|
17287
|
-
|
|
17301
|
+
onReasoning?.(`
|
|
17288
17302
|
|
|
17289
17303
|
`);
|
|
17290
17304
|
}
|
|
@@ -17297,7 +17311,7 @@ class Agent {
|
|
|
17297
17311
|
if (config.showReasoning) {
|
|
17298
17312
|
const metaOut = pluginManager.runOnMeta({ iteration: state.iteration, logger, contextManager }, chunk.content);
|
|
17299
17313
|
if (metaOut) {
|
|
17300
|
-
|
|
17314
|
+
onReasoning?.(metaOut);
|
|
17301
17315
|
}
|
|
17302
17316
|
emittedReasoning = true;
|
|
17303
17317
|
}
|
|
@@ -17686,7 +17700,6 @@ var init_agent = __esm(() => {
|
|
|
17686
17700
|
init_manager();
|
|
17687
17701
|
init_factory();
|
|
17688
17702
|
init_i18n();
|
|
17689
|
-
init_colors();
|
|
17690
17703
|
init_utils();
|
|
17691
17704
|
init_prompt_builder();
|
|
17692
17705
|
init_processes();
|
|
@@ -45601,7 +45614,7 @@ ${pc.green(t("repl.agent"))}`);
|
|
|
45601
45614
|
} else {
|
|
45602
45615
|
renderer.thinkingEnd();
|
|
45603
45616
|
}
|
|
45604
|
-
});
|
|
45617
|
+
}, (reasoning) => renderer.reasoning(reasoning));
|
|
45605
45618
|
renderer.flush();
|
|
45606
45619
|
if (result.provider && result.model) {
|
|
45607
45620
|
renderer.footer(result.model, result.provider, result.llmDurationMs ?? result.durationMs ?? 0);
|
|
@@ -46431,6 +46444,25 @@ function safeEqual(a, b) {
|
|
|
46431
46444
|
return false;
|
|
46432
46445
|
return timingSafeEqual2(ab, bb);
|
|
46433
46446
|
}
|
|
46447
|
+
var AUTH_COOKIE = "mma_web_auth";
|
|
46448
|
+
function cookieValue(header, name) {
|
|
46449
|
+
if (!header)
|
|
46450
|
+
return null;
|
|
46451
|
+
for (const part of header.split(";")) {
|
|
46452
|
+
const eq = part.indexOf("=");
|
|
46453
|
+
if (eq === -1)
|
|
46454
|
+
continue;
|
|
46455
|
+
if (part.slice(0, eq).trim() === name) {
|
|
46456
|
+
const raw = part.slice(eq + 1).trim();
|
|
46457
|
+
try {
|
|
46458
|
+
return decodeURIComponent(raw);
|
|
46459
|
+
} catch {
|
|
46460
|
+
return null;
|
|
46461
|
+
}
|
|
46462
|
+
}
|
|
46463
|
+
}
|
|
46464
|
+
return null;
|
|
46465
|
+
}
|
|
46434
46466
|
function checkAuth(req, reqUrl, s) {
|
|
46435
46467
|
if (!s.token)
|
|
46436
46468
|
return true;
|
|
@@ -46438,7 +46470,15 @@ function checkAuth(req, reqUrl, s) {
|
|
|
46438
46470
|
if (typeof header === "string" && safeEqual(header, s.token))
|
|
46439
46471
|
return true;
|
|
46440
46472
|
const query = reqUrl.searchParams.get("token");
|
|
46441
|
-
|
|
46473
|
+
if (query !== null && safeEqual(query, s.token))
|
|
46474
|
+
return true;
|
|
46475
|
+
const cookie = cookieValue(req.headers.cookie, AUTH_COOKIE);
|
|
46476
|
+
return cookie !== null && safeEqual(cookie, s.token);
|
|
46477
|
+
}
|
|
46478
|
+
function setAuthCookie(res, s) {
|
|
46479
|
+
if (!s.token)
|
|
46480
|
+
return;
|
|
46481
|
+
res.setHeader("Set-Cookie", `${AUTH_COOKIE}=${encodeURIComponent(s.token)}; Path=/; HttpOnly; SameSite=Strict`);
|
|
46442
46482
|
}
|
|
46443
46483
|
function sameOrigin(req, reqUrl) {
|
|
46444
46484
|
const origin = req.headers.origin;
|
|
@@ -46816,18 +46856,22 @@ function clipToolOutput(toolName, output) {
|
|
|
46816
46856
|
var DEFAULT_FLUSH_MS = 350;
|
|
46817
46857
|
function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
|
|
46818
46858
|
const flushMs = opts.flushMs ?? DEFAULT_FLUSH_MS;
|
|
46819
|
-
let buf = { text: "", meta: "" };
|
|
46859
|
+
let buf = { text: "", meta: "", reasoning: "" };
|
|
46820
46860
|
let timer = null;
|
|
46821
46861
|
const flush = () => {
|
|
46822
46862
|
if (timer)
|
|
46823
46863
|
clearTimeout(timer);
|
|
46824
46864
|
timer = null;
|
|
46865
|
+
if (buf.reasoning) {
|
|
46866
|
+
sink({ type: "reasoning", ts: Date.now(), chunk: stripAnsi2(buf.reasoning) });
|
|
46867
|
+
buf.reasoning = "";
|
|
46868
|
+
}
|
|
46825
46869
|
if (buf.text) {
|
|
46826
|
-
sink({ type: "text", ts: Date.now(), chunk: buf.text });
|
|
46870
|
+
sink({ type: "text", ts: Date.now(), chunk: stripAnsi2(buf.text) });
|
|
46827
46871
|
buf.text = "";
|
|
46828
46872
|
}
|
|
46829
46873
|
if (buf.meta) {
|
|
46830
|
-
sink({ type: "meta", ts: Date.now(), chunk: buf.meta });
|
|
46874
|
+
sink({ type: "meta", ts: Date.now(), chunk: stripAnsi2(buf.meta) });
|
|
46831
46875
|
buf.meta = "";
|
|
46832
46876
|
}
|
|
46833
46877
|
};
|
|
@@ -46844,6 +46888,10 @@ function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
|
|
|
46844
46888
|
buf.meta += chunk;
|
|
46845
46889
|
schedule();
|
|
46846
46890
|
},
|
|
46891
|
+
onReasoning(chunk) {
|
|
46892
|
+
buf.reasoning += chunk;
|
|
46893
|
+
schedule();
|
|
46894
|
+
},
|
|
46847
46895
|
onPhase(phase) {
|
|
46848
46896
|
flush();
|
|
46849
46897
|
sink({ type: "phase", ts: Date.now(), phase });
|
|
@@ -46867,7 +46915,7 @@ function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
|
|
|
46867
46915
|
name: call.name,
|
|
46868
46916
|
success: result.success,
|
|
46869
46917
|
durationMs,
|
|
46870
|
-
output: redactContextSecrets(clipToolOutput(call.name, result.output || ""))
|
|
46918
|
+
output: stripAnsi2(redactContextSecrets(clipToolOutput(call.name, result.output || "")))
|
|
46871
46919
|
});
|
|
46872
46920
|
},
|
|
46873
46921
|
onPlan(plan) {
|
|
@@ -47137,7 +47185,7 @@ function handleAnswerPost(req, res, s) {
|
|
|
47137
47185
|
// src/modules/webui/history-api.ts
|
|
47138
47186
|
init_context_log();
|
|
47139
47187
|
function scrub(text) {
|
|
47140
|
-
return text ? redactContextSecrets(text) : undefined;
|
|
47188
|
+
return text ? stripAnsi2(redactContextSecrets(text)) : undefined;
|
|
47141
47189
|
}
|
|
47142
47190
|
function mapLogEntry(e) {
|
|
47143
47191
|
switch (e.type) {
|
|
@@ -47766,6 +47814,106 @@ function handleProvidersApi(req, res, reqUrl, s) {
|
|
|
47766
47814
|
return false;
|
|
47767
47815
|
}
|
|
47768
47816
|
|
|
47817
|
+
// src/modules/webui/file-api.ts
|
|
47818
|
+
init_protected_paths();
|
|
47819
|
+
init_i18n();
|
|
47820
|
+
import { readFile as readFile2, realpath, stat as stat2 } from "node:fs/promises";
|
|
47821
|
+
import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve26, sep as sep3 } from "node:path";
|
|
47822
|
+
var MAX_FILE_VIEW_BYTES = 512 * 1024;
|
|
47823
|
+
function sendTextError(res, status2, key, vars) {
|
|
47824
|
+
res.writeHead(status2, {
|
|
47825
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
47826
|
+
"Cache-Control": "no-store"
|
|
47827
|
+
});
|
|
47828
|
+
res.end(t(key, vars));
|
|
47829
|
+
}
|
|
47830
|
+
function decodeText(buf) {
|
|
47831
|
+
if (buf.length >= 2 && buf[0] === 255 && buf[1] === 254) {
|
|
47832
|
+
return { text: buf.subarray(2).toString("utf16le"), binary: false };
|
|
47833
|
+
}
|
|
47834
|
+
if (buf.length >= 2 && buf[0] === 254 && buf[1] === 255) {
|
|
47835
|
+
const body2 = buf.subarray(2);
|
|
47836
|
+
const even = body2.length - body2.length % 2;
|
|
47837
|
+
const swapped = Buffer.from(body2.subarray(0, even));
|
|
47838
|
+
swapped.swap16();
|
|
47839
|
+
return { text: swapped.toString("utf16le"), binary: false };
|
|
47840
|
+
}
|
|
47841
|
+
const body = buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191 ? buf.subarray(3) : buf;
|
|
47842
|
+
if (body.includes(0))
|
|
47843
|
+
return { text: "", binary: true };
|
|
47844
|
+
return { text: body.toString("utf8"), binary: false };
|
|
47845
|
+
}
|
|
47846
|
+
function escapes(base, target) {
|
|
47847
|
+
const rel = relative10(base, target);
|
|
47848
|
+
return rel === ".." || rel.startsWith(".." + sep3) || isAbsolute6(rel);
|
|
47849
|
+
}
|
|
47850
|
+
function handleFileApi(req, res, reqUrl, s) {
|
|
47851
|
+
if (reqUrl.pathname !== "/api/file" || req.method !== "GET")
|
|
47852
|
+
return false;
|
|
47853
|
+
const base = s.workspace;
|
|
47854
|
+
if (!base) {
|
|
47855
|
+
sendTextError(res, 503, "web.file.error.workspace_unavailable");
|
|
47856
|
+
return true;
|
|
47857
|
+
}
|
|
47858
|
+
const raw = reqUrl.searchParams.get("path") ?? "";
|
|
47859
|
+
if (!raw) {
|
|
47860
|
+
sendTextError(res, 400, "web.file.error.missing_path");
|
|
47861
|
+
return true;
|
|
47862
|
+
}
|
|
47863
|
+
const abs = isAbsolute6(raw) ? resolve26(raw) : resolve26(base, raw);
|
|
47864
|
+
if (escapes(resolve26(base), abs)) {
|
|
47865
|
+
sendTextError(res, 403, "web.file.error.outside_workspace");
|
|
47866
|
+
return true;
|
|
47867
|
+
}
|
|
47868
|
+
serveFile(res, base, abs);
|
|
47869
|
+
return true;
|
|
47870
|
+
}
|
|
47871
|
+
async function serveFile(res, base, abs) {
|
|
47872
|
+
let baseReal;
|
|
47873
|
+
let absReal;
|
|
47874
|
+
try {
|
|
47875
|
+
baseReal = await realpath(resolve26(base));
|
|
47876
|
+
absReal = await realpath(abs);
|
|
47877
|
+
} catch {
|
|
47878
|
+
sendTextError(res, 404, "web.file.error.not_found");
|
|
47879
|
+
return;
|
|
47880
|
+
}
|
|
47881
|
+
if (escapes(baseReal, absReal)) {
|
|
47882
|
+
sendTextError(res, 403, "web.file.error.outside_workspace");
|
|
47883
|
+
return;
|
|
47884
|
+
}
|
|
47885
|
+
if (matchProtectedPath(baseReal, absReal)) {
|
|
47886
|
+
sendTextError(res, 403, "web.file.error.protected_path");
|
|
47887
|
+
return;
|
|
47888
|
+
}
|
|
47889
|
+
try {
|
|
47890
|
+
const info = await stat2(absReal);
|
|
47891
|
+
if (!info.isFile()) {
|
|
47892
|
+
sendTextError(res, 404, "web.file.error.not_found");
|
|
47893
|
+
return;
|
|
47894
|
+
}
|
|
47895
|
+
if (info.size > MAX_FILE_VIEW_BYTES) {
|
|
47896
|
+
sendTextError(res, 413, "web.file.error.too_large", {
|
|
47897
|
+
limit: String(MAX_FILE_VIEW_BYTES)
|
|
47898
|
+
});
|
|
47899
|
+
return;
|
|
47900
|
+
}
|
|
47901
|
+
const buf = await readFile2(absReal);
|
|
47902
|
+
const { text, binary } = decodeText(buf);
|
|
47903
|
+
if (binary) {
|
|
47904
|
+
sendTextError(res, 415, "web.file.error.binary_file");
|
|
47905
|
+
return;
|
|
47906
|
+
}
|
|
47907
|
+
res.writeHead(200, {
|
|
47908
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
47909
|
+
"Cache-Control": "no-store"
|
|
47910
|
+
});
|
|
47911
|
+
res.end(text);
|
|
47912
|
+
} catch {
|
|
47913
|
+
sendTextError(res, 404, "web.file.error.not_found");
|
|
47914
|
+
}
|
|
47915
|
+
}
|
|
47916
|
+
|
|
47769
47917
|
// src/modules/webui/server.ts
|
|
47770
47918
|
var MAX_CLIENTS = parseMaxClients(process.env.MMA_WEBUI_MAX_CLIENTS);
|
|
47771
47919
|
function parseMaxClients(raw) {
|
|
@@ -47859,6 +48007,9 @@ function handleApi(req, res, reqUrl, s) {
|
|
|
47859
48007
|
if (handleProvidersApi(req, res, reqUrl, s)) {
|
|
47860
48008
|
return;
|
|
47861
48009
|
}
|
|
48010
|
+
if (handleFileApi(req, res, reqUrl, s)) {
|
|
48011
|
+
return;
|
|
48012
|
+
}
|
|
47862
48013
|
if (reqUrl.pathname === "/api/models" && req.method === "GET") {
|
|
47863
48014
|
handleModelsGet(res, s);
|
|
47864
48015
|
return;
|
|
@@ -47903,6 +48054,7 @@ function handleRequest(req, res, assets, s) {
|
|
|
47903
48054
|
sendJson(res, 401, { error: "unauthorized" });
|
|
47904
48055
|
return;
|
|
47905
48056
|
}
|
|
48057
|
+
setAuthCookie(res, s);
|
|
47906
48058
|
handleEvents(req, res, s);
|
|
47907
48059
|
return;
|
|
47908
48060
|
}
|
|
@@ -47921,6 +48073,7 @@ function handleRequest(req, res, assets, s) {
|
|
|
47921
48073
|
sendJson(res, 403, { error: "cross-origin requests are not allowed" });
|
|
47922
48074
|
return;
|
|
47923
48075
|
}
|
|
48076
|
+
setAuthCookie(res, s);
|
|
47924
48077
|
handleApi(req, res, reqUrl, s);
|
|
47925
48078
|
return;
|
|
47926
48079
|
}
|
|
@@ -47942,7 +48095,7 @@ function startServer(assets, s = getDefaultState()) {
|
|
|
47942
48095
|
return Promise.reject(new Error(`refusing to bind ${s.host} without a token (non-loopback)`));
|
|
47943
48096
|
}
|
|
47944
48097
|
const desired = Number(process.env.MMA_WEBUI_PORT) || 0;
|
|
47945
|
-
s.starting = new Promise((
|
|
48098
|
+
s.starting = new Promise((resolve27, reject) => {
|
|
47946
48099
|
const srv = createServer((req, res) => handleRequest(req, res, assets, s));
|
|
47947
48100
|
srv.on("error", (err) => {
|
|
47948
48101
|
s.starting = null;
|
|
@@ -47954,7 +48107,7 @@ function startServer(assets, s = getDefaultState()) {
|
|
|
47954
48107
|
const port = typeof addr === "object" && addr ? addr.port : desired;
|
|
47955
48108
|
s.url = `http://${s.host}:${port}`;
|
|
47956
48109
|
s.starting = null;
|
|
47957
|
-
|
|
48110
|
+
resolve27(s.url);
|
|
47958
48111
|
});
|
|
47959
48112
|
});
|
|
47960
48113
|
return s.starting;
|
|
@@ -48115,7 +48268,7 @@ async function startWebUi(deps) {
|
|
|
48115
48268
|
if (plan)
|
|
48116
48269
|
forwarder.onPlan(plan);
|
|
48117
48270
|
}
|
|
48118
|
-
}, (phase) => forwarder.onPhase(phase));
|
|
48271
|
+
}, (phase) => forwarder.onPhase(phase), (reasoning) => forwarder.onReasoning(reasoning));
|
|
48119
48272
|
state.usage = captureUsage(result, deps.config.contextWindow);
|
|
48120
48273
|
forwarder.onTurnEnd({
|
|
48121
48274
|
success: result.success,
|
|
@@ -48244,13 +48397,13 @@ async function runWeb(opts) {
|
|
|
48244
48397
|
getDefaultChannel().writeLine(t("cli.web_started", { url }));
|
|
48245
48398
|
if (!opts.noOpen)
|
|
48246
48399
|
tryOpenBrowser(url);
|
|
48247
|
-
await new Promise((
|
|
48400
|
+
await new Promise((resolve27) => {
|
|
48248
48401
|
const shutdown = () => {
|
|
48249
48402
|
process.off("SIGINT", shutdown);
|
|
48250
48403
|
process.off("SIGTERM", shutdown);
|
|
48251
48404
|
web.stop();
|
|
48252
48405
|
agent.shutdown();
|
|
48253
|
-
|
|
48406
|
+
resolve27();
|
|
48254
48407
|
};
|
|
48255
48408
|
process.on("SIGINT", shutdown);
|
|
48256
48409
|
process.on("SIGTERM", shutdown);
|
|
@@ -48676,7 +48829,7 @@ async function main() {
|
|
|
48676
48829
|
} else {
|
|
48677
48830
|
renderer.thinkingEnd();
|
|
48678
48831
|
}
|
|
48679
|
-
});
|
|
48832
|
+
}, (reasoning) => renderer.reasoning(reasoning));
|
|
48680
48833
|
} catch (err) {
|
|
48681
48834
|
result = {
|
|
48682
48835
|
success: false,
|