@xingjisoft/neutrino 0.6.0 → 0.7.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/README.md +6 -1
- package/assets/fast-planner.txt +57 -0
- package/assets/fast-replanner.txt +28 -0
- package/dist/agents.d.ts +25 -0
- package/dist/app.d.ts +206 -0
- package/dist/app.js +15 -0
- package/dist/background.d.ts +96 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +40 -3
- package/dist/browser.d.ts +18 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/colors.d.ts +29 -0
- package/dist/compact.d.ts +52 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.js +22 -7
- package/dist/cwd.d.ts +19 -0
- package/dist/fast/artifacts.d.ts +26 -0
- package/dist/fast/artifacts.js +109 -0
- package/dist/fast/engine.d.ts +78 -0
- package/dist/fast/engine.js +657 -0
- package/dist/fast/evidence.d.ts +21 -0
- package/dist/fast/evidence.js +68 -0
- package/dist/fast/planner.d.ts +10 -0
- package/dist/fast/planner.js +51 -0
- package/dist/fast/pool.d.ts +17 -0
- package/dist/fast/pool.js +78 -0
- package/dist/fast/scheduler.d.ts +54 -0
- package/dist/fast/scheduler.js +383 -0
- package/dist/fast/types.d.ts +100 -0
- package/dist/fast/types.js +21 -0
- package/dist/fast/worker.d.ts +23 -0
- package/dist/fast/worker.js +185 -0
- package/dist/header.d.ts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/llm/approval.d.ts +204 -0
- package/dist/llm/approval.js +14 -3
- package/dist/llm/client.d.ts +118 -0
- package/dist/llm/client.js +57 -8
- package/dist/llm/codeExecutor.d.ts +14 -0
- package/dist/llm/codemode.d.ts +15 -0
- package/dist/llm/context.d.ts +29 -0
- package/dist/llm/context.js +17 -4
- package/dist/llm/diagnostics.d.ts +91 -0
- package/dist/llm/diagnostics.js +242 -0
- package/dist/llm/plan.d.ts +148 -0
- package/dist/llm/retry.d.ts +80 -0
- package/dist/llm/risk.d.ts +21 -0
- package/dist/llm/risk.js +7 -0
- package/dist/llm/runtime.d.ts +189 -0
- package/dist/llm/runtime.js +178 -24
- package/dist/llm/search.d.ts +2 -0
- package/dist/llm/searchNetwork.d.ts +8 -0
- package/dist/llm/subagent.d.ts +112 -0
- package/dist/llm/subagent.js +25 -12
- package/dist/llm/tools.d.ts +20 -0
- package/dist/llm/tools.js +4 -1
- package/dist/llm/workspace.d.ts +6 -0
- package/dist/llm/workspace.js +271 -0
- package/dist/markdown.d.ts +58 -0
- package/dist/migration.d.ts +8 -0
- package/dist/persistence.d.ts +14 -0
- package/dist/plugins/catalog.d.ts +20 -0
- package/dist/plugins/catalog.js +45 -0
- package/dist/plugins/cli.d.ts +1 -0
- package/dist/plugins/cli.js +106 -0
- package/dist/plugins/host.d.ts +121 -0
- package/dist/plugins/host.js +450 -0
- package/dist/plugins/manager.d.ts +24 -0
- package/dist/plugins/manager.js +288 -0
- package/dist/plugins/package.d.ts +5 -0
- package/dist/plugins/package.js +53 -0
- package/dist/plugins/runner.d.ts +1 -0
- package/dist/plugins/runner.js +227 -0
- package/dist/plugins/session.d.ts +12 -0
- package/dist/plugins/session.js +97 -0
- package/dist/plugins/terminal.d.ts +22 -0
- package/dist/plugins/terminal.js +33 -0
- package/dist/plugins/types.d.ts +178 -0
- package/dist/plugins/types.js +6 -0
- package/dist/plugins/validation.d.ts +14 -0
- package/dist/plugins/validation.js +174 -0
- package/dist/plugins/web-sdk.d.ts +58 -0
- package/dist/plugins/web-sdk.js +1 -0
- package/dist/pro/agents.d.ts +34 -0
- package/dist/pro/agents.js +1 -1
- package/dist/pro/config.d.ts +75 -0
- package/dist/pro/config.js +3 -2
- package/dist/pro/preflight.d.ts +21 -0
- package/dist/sanitize.d.ts +34 -0
- package/dist/screens/agentConversation.d.ts +28 -0
- package/dist/screens/agentHistory.d.ts +25 -0
- package/dist/screens/agentSelect.d.ts +16 -0
- package/dist/screens/chat.d.ts +292 -0
- package/dist/screens/chat.js +179 -65
- package/dist/screens/history.d.ts +23 -0
- package/dist/screens/modelSelect.d.ts +29 -0
- package/dist/screens/plugins.d.ts +29 -0
- package/dist/screens/plugins.js +196 -0
- package/dist/screens/settings.d.ts +42 -0
- package/dist/screens/start.d.ts +43 -0
- package/dist/screens/start.js +25 -17
- package/dist/screens/updatePrompt.d.ts +41 -0
- package/dist/selection.d.ts +75 -0
- package/dist/state.d.ts +26 -0
- package/dist/terminal/buffer.d.ts +47 -0
- package/dist/terminal/input.d.ts +70 -0
- package/dist/terminal/renderer.d.ts +43 -0
- package/dist/terminal/restore.d.ts +41 -0
- package/dist/terminal/screen.d.ts +57 -0
- package/dist/tray.d.ts +14 -0
- package/dist/ui.d.ts +123 -0
- package/dist/update.d.ts +71 -0
- package/dist/updateWatch.d.ts +43 -0
- package/dist/web/approvalQueue.d.ts +8 -0
- package/dist/web/approvalQueue.js +59 -0
- package/dist/web/auth.d.ts +14 -0
- package/dist/web/clientState.d.ts +16 -0
- package/dist/web/folderPicker.d.ts +34 -0
- package/dist/web/folderPicker.js +544 -0
- package/dist/web/hub.d.ts +213 -0
- package/dist/web/hub.js +14 -2
- package/dist/web/localApi.d.ts +11 -0
- package/dist/web/localApi.js +260 -0
- package/dist/web/plugins.d.ts +8 -0
- package/dist/web/plugins.js +64 -0
- package/dist/web/server.d.ts +81 -0
- package/dist/web/server.js +127 -19
- package/dist/web/service.d.ts +7 -0
- package/dist/web/service.js +1 -0
- package/dist/web/session.d.ts +176 -0
- package/dist/web/session.js +170 -21
- package/dist/web/settings.d.ts +65 -0
- package/dist/web/terminalBridge.d.ts +15 -0
- package/dist/web/terminalBridge.js +8 -2
- package/dist/width.d.ts +6 -0
- package/docs/plugins/README.md +126 -0
- package/docs/plugins/agent-sdk.md +92 -0
- package/docs/plugins/architecture.md +64 -0
- package/docs/plugins/compatibility.md +34 -0
- package/docs/plugins/development.md +55 -0
- package/docs/plugins/manifest.md +59 -0
- package/docs/plugins/manifest.schema.json +286 -0
- package/docs/plugins/security.md +53 -0
- package/docs/plugins/skills-prompts.md +69 -0
- package/docs/plugins/ui-sdk.md +109 -0
- package/examples/local-api.py +105 -0
- package/examples/plugins/cards/card.html +6 -0
- package/examples/plugins/cards/neutrino.plugin.json +42 -0
- package/examples/plugins/composer/editor.html +10 -0
- package/examples/plugins/composer/neutrino.plugin.json +10 -0
- package/examples/plugins/function-tools/agent.js +32 -0
- package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/panel.html +6 -0
- package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
- package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
- package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
- package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
- package/examples/plugins/theme/neutrino.plugin.json +12 -0
- package/package.json +66 -42
- package/web/LOCAL-API.md +65 -0
- package/web/README.md +30 -8
- package/web/app.js +256 -67
- package/web/i18n.js +18 -3
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +12 -3
- package/web/turns.js +7 -8
package/web/app.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { createPluginUI } from "./plugins.js";
|
|
2
|
+
let pluginUI = null;
|
|
1
3
|
import { createSettingsUI } from "./settings.js";
|
|
2
4
|
import { createProUI } from "./pro.js";
|
|
3
5
|
import { renderMarkdown as renderMarkdownHtml } from "./markdown.js";
|
|
4
|
-
import { turnAnswerIndex, turnFolds,
|
|
6
|
+
import { turnAnswerIndex, turnFolds, turnRanges } from "./turns.js";
|
|
5
7
|
import { applyTranslations, getLanguage as currentLanguage, locale as currentLocale, setLanguage as setI18nLanguage, t as translate } from "./i18n.js";
|
|
6
8
|
|
|
7
9
|
// Neutrino web UI — a dependency-free single page app.
|
|
@@ -596,9 +598,9 @@ function storedWorkspace() {
|
|
|
596
598
|
return { key: pathKey(path), path };
|
|
597
599
|
}
|
|
598
600
|
|
|
599
|
-
const AGENT_MODE_LABELS = { standard: "Standard mode", ptc: "PTC mode", fast: "Fast mode", pro: "Professional mode" };
|
|
601
|
+
const AGENT_MODE_LABELS = { standard: "Standard mode", ptc: "PTC mode", fast: "Fast mode", fast_dag: "Fast Mode", pro: "Professional mode" };
|
|
600
602
|
let newAgentMode = readStorage("neutrino.agent-mode");
|
|
601
|
-
if (!["standard", "ptc", "fast"].includes(newAgentMode)) newAgentMode = "standard";
|
|
603
|
+
if (!["standard", "ptc", "fast", "fast_dag"].includes(newAgentMode)) newAgentMode = "standard";
|
|
602
604
|
let agentModePending = Promise.resolve();
|
|
603
605
|
let agentModeChanging = false;
|
|
604
606
|
let workspaceNavigation = false;
|
|
@@ -625,7 +627,8 @@ function renderAgentMode() {
|
|
|
625
627
|
el.modePicker.classList.toggle("is-pending", Boolean(agentModeChanging || nextTurn));
|
|
626
628
|
for (const button of el.modePicker.querySelectorAll?.("[data-agent-mode]") || []) {
|
|
627
629
|
button.setAttribute("aria-checked", String(button.dataset.agentMode === mode));
|
|
628
|
-
button.disabled = agentModeChanging || workspaceNavigation;
|
|
630
|
+
button.disabled = agentModeChanging || workspaceNavigation || (button.dataset.agentMode === "fast_dag" && state.overview?.fast_mode_enabled === false);
|
|
631
|
+
if (button.dataset.agentMode === "fast_dag") button.title = state.overview?.fast_mode_enabled === false ? tr("Fast Mode is disabled; select another mode.") : "";
|
|
629
632
|
}
|
|
630
633
|
if (el.modeFeedback) {
|
|
631
634
|
el.modeFeedback.textContent = agentModeError || (agentModeChanging ? tr("Changing mode…") : nextTurn ? tr("Applies to the next turn") : "");
|
|
@@ -727,7 +730,7 @@ function initializeAgentMode() {
|
|
|
727
730
|
if (event.key === "Escape") { el.modePicker.open = false; el.modePicker.querySelector("summary")?.focus(); event.stopPropagation(); }
|
|
728
731
|
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
729
732
|
event.preventDefault(); el.modePicker.open = true;
|
|
730
|
-
const buttons = [...el.modePicker.querySelectorAll("[data-agent-mode]")];
|
|
733
|
+
const buttons = [...el.modePicker.querySelectorAll("[data-agent-mode]")].filter(button => !button.disabled);
|
|
731
734
|
let index = buttons.indexOf(document.activeElement);
|
|
732
735
|
index = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
733
736
|
buttons[index]?.focus();
|
|
@@ -983,6 +986,7 @@ async function loadOverview(options = {}) {
|
|
|
983
986
|
const overview = await response.json();
|
|
984
987
|
if (gateShown) return;
|
|
985
988
|
state.overview = overview;
|
|
989
|
+
if (el.modePicker) renderAgentMode();
|
|
986
990
|
setStatus(state.selection?.kind === "live" ? "live" : "online", state.selection?.kind === "live" ? "live" : "online");
|
|
987
991
|
renderWorkspaceChrome();
|
|
988
992
|
renderSidebar();
|
|
@@ -1628,14 +1632,32 @@ function canPauseFromComposer() {
|
|
|
1628
1632
|
&& snapshot.busy && !snapshot.paused && !el.input.value.trim());
|
|
1629
1633
|
}
|
|
1630
1634
|
|
|
1635
|
+
// A paused turn resumes from the composer button, exactly like an empty draft
|
|
1636
|
+
// pauses a running one: the draft wins, so typing always means "send".
|
|
1637
|
+
function canResumeFromComposer() {
|
|
1638
|
+
const selection = state.selection;
|
|
1639
|
+
const snapshot = state.snapshot;
|
|
1640
|
+
return Boolean(selection?.kind === "live" && snapshot?.key === selection.key
|
|
1641
|
+
&& snapshot.paused && !el.input.value.trim());
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function composerAction() {
|
|
1645
|
+
if (canPauseFromComposer()) return "pause";
|
|
1646
|
+
if (canResumeFromComposer()) return "resume";
|
|
1647
|
+
return "send";
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1631
1650
|
function renderComposerAction() {
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
el.send.classList.toggle("is-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
el.send.setAttribute("
|
|
1651
|
+
const action = composerAction();
|
|
1652
|
+
el.send.classList.toggle("is-pause", action === "pause");
|
|
1653
|
+
el.send.classList.toggle("is-resume", action === "resume");
|
|
1654
|
+
const [source, text] = action === "pause" ? ["Pause", tr("Pause")]
|
|
1655
|
+
: action === "resume" ? ["Resume", tr("Resume")]
|
|
1656
|
+
: ["Send message", tr("Send message")];
|
|
1657
|
+
el.send.setAttribute("data-i18n-title", source);
|
|
1658
|
+
el.send.setAttribute("data-i18n-aria-label", source);
|
|
1659
|
+
el.send.title = text;
|
|
1660
|
+
el.send.setAttribute("aria-label", text);
|
|
1639
1661
|
}
|
|
1640
1662
|
|
|
1641
1663
|
function hideComposer() {
|
|
@@ -1885,7 +1907,7 @@ function renderControls(snapshot) {
|
|
|
1885
1907
|
? reviewStatus(reviewing)
|
|
1886
1908
|
: subagent
|
|
1887
1909
|
? tr("sub-agent: {label}", { label: subagent })
|
|
1888
|
-
: tr("working")
|
|
1910
|
+
: snapshot.fast && snapshot.activeAgentMode === "fast_dag" ? fastPhaseLabel(snapshot.fast.phase) : tr("working")
|
|
1889
1911
|
: tr("idle");
|
|
1890
1912
|
el.controls.appendChild(badge);
|
|
1891
1913
|
|
|
@@ -1896,12 +1918,15 @@ function renderControls(snapshot) {
|
|
|
1896
1918
|
el.controls.appendChild(warning);
|
|
1897
1919
|
}
|
|
1898
1920
|
|
|
1899
|
-
|
|
1921
|
+
// Resuming belongs to the composer button (see renderComposerAction), which
|
|
1922
|
+
// offers "Continue" on an empty draft, so the status bar never shows a second
|
|
1923
|
+
// way in. Pausing stays here, for a turn that is actually running.
|
|
1924
|
+
if (snapshot.busy && !snapshot.paused) {
|
|
1900
1925
|
const button = document.createElement("button");
|
|
1901
1926
|
button.className = "control";
|
|
1902
|
-
button.textContent =
|
|
1927
|
+
button.textContent = tr("Pause");
|
|
1903
1928
|
button.addEventListener("click", () => {
|
|
1904
|
-
postAction(snapshot.key,
|
|
1929
|
+
postAction(snapshot.key, "pause");
|
|
1905
1930
|
});
|
|
1906
1931
|
el.controls.appendChild(button);
|
|
1907
1932
|
}
|
|
@@ -1953,30 +1978,48 @@ function renderMessageList(messages, streaming, options) {
|
|
|
1953
1978
|
context.end <= original.length && typeof context.summary === "string" && context.summary.trim()
|
|
1954
1979
|
? context.end : 0;
|
|
1955
1980
|
if (end) {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1981
|
+
// Keep user input in place and fold only the AI output between prompts.
|
|
1982
|
+
// The disclosures share one expansion state for this compressed prefix.
|
|
1983
|
+
const sections = [];
|
|
1984
|
+
for (let index = 0; index < end;) {
|
|
1985
|
+
if (original[index].role === "user") {
|
|
1986
|
+
el.messages.appendChild(renderMessage(original[index], index));
|
|
1987
|
+
index++;
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
const start = index;
|
|
1991
|
+
while (index < end && original[index].role !== "user") index++;
|
|
1992
|
+
const archived = original.slice(start, index);
|
|
1993
|
+
const details = document.createElement("details");
|
|
1994
|
+
details.className = "conversation-history";
|
|
1995
|
+
const summary = document.createElement("summary");
|
|
1996
|
+
summary.textContent = typeof tr === "function"
|
|
1997
|
+
? tr("Earlier conversation · {n} messages", { n: archived.length })
|
|
1998
|
+
: `Earlier conversation · ${archived.length} messages`;
|
|
1999
|
+
details.appendChild(summary);
|
|
2000
|
+
const body = document.createElement("div");
|
|
2001
|
+
body.className = "history-messages";
|
|
2002
|
+
details.appendChild(body);
|
|
2003
|
+
const populate = () => {
|
|
2004
|
+
if (body.childNodes.length) return;
|
|
2005
|
+
archived.forEach((message, offset) => body.appendChild(renderMessage(message, start + offset)));
|
|
2006
|
+
};
|
|
2007
|
+
const sync = (open) => {
|
|
2008
|
+
if (details.open !== open) details.open = open;
|
|
2009
|
+
if (open) populate();
|
|
2010
|
+
else body.replaceChildren();
|
|
2011
|
+
};
|
|
2012
|
+
sections.push(sync);
|
|
2013
|
+
sync(state.openContext === end);
|
|
2014
|
+
details.addEventListener("toggle", () => {
|
|
2015
|
+
// Detached disclosures can emit a delayed toggle during a stream repaint.
|
|
2016
|
+
if (!details.isConnected) return;
|
|
2017
|
+
const open = details.open;
|
|
2018
|
+
state.openContext = open ? end : null;
|
|
2019
|
+
for (const sync of sections) sync(open);
|
|
2020
|
+
});
|
|
2021
|
+
el.messages.appendChild(details);
|
|
2022
|
+
}
|
|
1980
2023
|
}
|
|
1981
2024
|
// Render the tail one turn at a time: a finished turn folds its work behind a
|
|
1982
2025
|
// summary line and keeps its reply; a running one stays fully open. The turn in
|
|
@@ -2010,6 +2053,7 @@ function renderMessageList(messages, streaming, options) {
|
|
|
2010
2053
|
function renderMessage(message, index) {
|
|
2011
2054
|
const article = document.createElement("article");
|
|
2012
2055
|
article.className = `message ${message.role}`;
|
|
2056
|
+
article.dataset.pluginMessage = String(index);
|
|
2013
2057
|
|
|
2014
2058
|
if (message.role === "tool") {
|
|
2015
2059
|
const details = document.createElement("details");
|
|
@@ -2058,8 +2102,8 @@ function renderMessage(message, index) {
|
|
|
2058
2102
|
}
|
|
2059
2103
|
|
|
2060
2104
|
/**
|
|
2061
|
-
* A finished turn: its
|
|
2062
|
-
*
|
|
2105
|
+
* A finished turn: its complete user input above the fold, its reply below it.
|
|
2106
|
+
* The folded AI messages are rendered on first open and dropped
|
|
2063
2107
|
* again on close, so a long transcript costs nothing until the reader asks for
|
|
2064
2108
|
* it. Nothing is lost either way: this is presentation only, and the saved
|
|
2065
2109
|
* transcript always holds every message.
|
|
@@ -2071,25 +2115,27 @@ function renderTurnFold(start, messages) {
|
|
|
2071
2115
|
? tr(text, values)
|
|
2072
2116
|
: String(text).replace(/\{(\w+)\}/g, (match, name) =>
|
|
2073
2117
|
values && Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : match));
|
|
2074
|
-
//
|
|
2075
|
-
// prompt line plus the work it hides) and, right below it, the answer itself.
|
|
2118
|
+
// User input and the reply stay outside the disclosure, in transcript order.
|
|
2076
2119
|
const reply = turnAnswerIndex(messages);
|
|
2077
2120
|
const group = document.createElement("div");
|
|
2078
2121
|
group.className = "turn-group";
|
|
2122
|
+
messages.forEach((message, offset) => {
|
|
2123
|
+
if (message.role === "user") group.appendChild(renderMessage(message, start + offset));
|
|
2124
|
+
});
|
|
2079
2125
|
|
|
2080
2126
|
const details = document.createElement("details");
|
|
2081
2127
|
details.className = "turn";
|
|
2082
2128
|
details.open = state.openTurns.has(start);
|
|
2083
2129
|
|
|
2084
|
-
const hidden =
|
|
2130
|
+
const hidden = messages.filter((message, offset) => message.role !== "user" && offset !== reply).length;
|
|
2085
2131
|
const summary = document.createElement("summary");
|
|
2086
2132
|
const chevron = document.createElement("span");
|
|
2087
2133
|
chevron.className = "turn-chevron";
|
|
2088
2134
|
chevron.innerHTML =
|
|
2089
2135
|
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>';
|
|
2090
|
-
const
|
|
2091
|
-
|
|
2092
|
-
|
|
2136
|
+
const label = document.createElement("span");
|
|
2137
|
+
label.className = "turn-label";
|
|
2138
|
+
label.textContent = "Neutrino";
|
|
2093
2139
|
const meta = document.createElement("span");
|
|
2094
2140
|
meta.className = "turn-meta";
|
|
2095
2141
|
meta.textContent = tx("{n} of {m} messages folded", { n: hidden, m: messages.length });
|
|
@@ -2098,7 +2144,7 @@ function renderTurnFold(start, messages) {
|
|
|
2098
2144
|
hint.className = "turn-hint";
|
|
2099
2145
|
meta.appendChild(hint);
|
|
2100
2146
|
summary.appendChild(chevron);
|
|
2101
|
-
summary.appendChild(
|
|
2147
|
+
summary.appendChild(label);
|
|
2102
2148
|
summary.appendChild(meta);
|
|
2103
2149
|
details.appendChild(summary);
|
|
2104
2150
|
group.appendChild(details);
|
|
@@ -2114,7 +2160,7 @@ function renderTurnFold(start, messages) {
|
|
|
2114
2160
|
const populate = () => {
|
|
2115
2161
|
if (body.childNodes.length) return;
|
|
2116
2162
|
messages.forEach((message, offset) => {
|
|
2117
|
-
if (offset === reply) return;
|
|
2163
|
+
if (message.role === "user" || offset === reply) return;
|
|
2118
2164
|
body.appendChild(renderMessage(message, start + offset));
|
|
2119
2165
|
});
|
|
2120
2166
|
};
|
|
@@ -2158,52 +2204,69 @@ function scrollToBottom() {
|
|
|
2158
2204
|
|
|
2159
2205
|
async function postAction(key, action, body) {
|
|
2160
2206
|
try {
|
|
2161
|
-
await apiFetch(`/api/live/${encodeURIComponent(key)}/${action}`, {
|
|
2207
|
+
const response = await apiFetch(`/api/live/${encodeURIComponent(key)}/${action}`, {
|
|
2162
2208
|
method: "POST",
|
|
2163
2209
|
headers: body ? { "Content-Type": "application/json" } : undefined,
|
|
2164
2210
|
body: body ? JSON.stringify(body) : undefined,
|
|
2165
2211
|
});
|
|
2212
|
+
if (!response.ok) { const data = await response.json(); throw new Error(data.error || `HTTP ${response.status}`); }
|
|
2213
|
+
return true;
|
|
2166
2214
|
} catch (error) {
|
|
2215
|
+
el.sub.textContent = error.message;
|
|
2167
2216
|
console.error(error);
|
|
2217
|
+
return false;
|
|
2168
2218
|
}
|
|
2169
2219
|
}
|
|
2170
2220
|
|
|
2171
2221
|
function activateComposer() {
|
|
2172
|
-
|
|
2222
|
+
const action = composerAction();
|
|
2223
|
+
if (action === "pause" || action === "resume") return postAction(state.selection.key, action);
|
|
2173
2224
|
return sendMessage();
|
|
2174
2225
|
}
|
|
2175
2226
|
|
|
2176
2227
|
async function sendMessage() {
|
|
2228
|
+
// Internal plugin requests pin their originating selection and can preserve the user draft.
|
|
2229
|
+
const pluginRequest = arguments[0] ?? null;
|
|
2230
|
+
// The keydown handler passes the text it saw: sendMessage awaits the agent-mode
|
|
2231
|
+
// preference before touching the textarea, so reading it here is too late.
|
|
2232
|
+
const submittedText = arguments[1] ?? null;
|
|
2177
2233
|
try { await agentModePending; } catch (error) { el.sub.textContent = error.message; return; }
|
|
2234
|
+
if (pluginRequest && state.selection !== pluginRequest.selection) throw new Error("Conversation changed before the plugin message was sent");
|
|
2178
2235
|
const selection = state.selection;
|
|
2179
2236
|
if (!selection) return;
|
|
2180
|
-
const text = el.input.value;
|
|
2237
|
+
const text = pluginRequest?.text ?? submittedText ?? el.input.value;
|
|
2181
2238
|
if (!text.trim()) return;
|
|
2182
2239
|
try {
|
|
2183
2240
|
if (PRO_MODE) await proUI?.flush();
|
|
2184
2241
|
else await preferences.flush?.();
|
|
2185
2242
|
} catch (error) { el.sub.textContent = tr(error.message); return; }
|
|
2186
|
-
if (state.selection !== selection) return;
|
|
2243
|
+
if (state.selection !== selection) { if (pluginRequest) throw new Error("Conversation changed before the plugin message was sent"); return; }
|
|
2244
|
+
const messagePayload = { text, ...(PRO_MODE ? {} : { agent_mode: currentAgentMode() }) };
|
|
2187
2245
|
|
|
2188
2246
|
if (selection.kind === "live") {
|
|
2189
|
-
el.input.value = "";
|
|
2247
|
+
if (!pluginRequest?.preserveDraft) el.input.value = "";
|
|
2190
2248
|
autosize();
|
|
2191
|
-
|
|
2249
|
+
// A paused turn only queues the text, so a user send resumes right away: the
|
|
2250
|
+
// message then starts the turn instead of waiting for a separate continue click.
|
|
2251
|
+
const resumes = !pluginRequest && state.snapshot?.key === selection.key && Boolean(state.snapshot.paused);
|
|
2252
|
+
const sent = await postAction(selection.key, "message", messagePayload);
|
|
2253
|
+
if (sent === false) { if (!pluginRequest?.preserveDraft) el.input.value = text; autosize(); }
|
|
2254
|
+
else if (resumes) await postAction(selection.key, "resume");
|
|
2192
2255
|
return;
|
|
2193
2256
|
}
|
|
2194
2257
|
|
|
2195
2258
|
if (selection.kind === "new") {
|
|
2196
2259
|
// A workspace landing page: typing starts a fresh conversation there, just
|
|
2197
2260
|
// like typing on the terminal's start screen.
|
|
2198
|
-
el.input.value = "";
|
|
2261
|
+
if (!pluginRequest?.preserveDraft) el.input.value = "";
|
|
2199
2262
|
autosize();
|
|
2200
2263
|
try {
|
|
2201
2264
|
const created = await createConversation(selection.cwd, preferences.defaultSelection?.());
|
|
2202
2265
|
await selectLive(created.key);
|
|
2203
2266
|
setConversationView(true);
|
|
2204
|
-
await postAction(created.key, "message", { text
|
|
2267
|
+
if (await postAction(created.key, "message", messagePayload) === false) { if (!pluginRequest?.preserveDraft) el.input.value = text; autosize(); }
|
|
2205
2268
|
} catch (error) {
|
|
2206
|
-
el.input.value = text;
|
|
2269
|
+
if (!pluginRequest?.preserveDraft) el.input.value = text;
|
|
2207
2270
|
autosize();
|
|
2208
2271
|
el.sub.textContent = tr("Could not start the conversation: {error}", { error: error.message });
|
|
2209
2272
|
}
|
|
@@ -2211,13 +2274,13 @@ async function sendMessage() {
|
|
|
2211
2274
|
}
|
|
2212
2275
|
|
|
2213
2276
|
// Reading a saved transcript: resume it in the browser, then deliver the text.
|
|
2214
|
-
el.input.value = "";
|
|
2277
|
+
if (!pluginRequest?.preserveDraft) el.input.value = "";
|
|
2215
2278
|
autosize();
|
|
2216
2279
|
try {
|
|
2217
2280
|
const key = await continueSession(selection.id);
|
|
2218
|
-
await postAction(key, "message", { text
|
|
2281
|
+
if (await postAction(key, "message", messagePayload) === false) { if (!pluginRequest?.preserveDraft) el.input.value = text; autosize(); }
|
|
2219
2282
|
} catch (error) {
|
|
2220
|
-
el.input.value = text;
|
|
2283
|
+
if (!pluginRequest?.preserveDraft) el.input.value = text;
|
|
2221
2284
|
autosize();
|
|
2222
2285
|
el.sub.textContent = tr("Could not continue this conversation: {error}", { error: error.message });
|
|
2223
2286
|
}
|
|
@@ -2689,6 +2752,46 @@ async function loadFolder(path) {
|
|
|
2689
2752
|
}
|
|
2690
2753
|
}
|
|
2691
2754
|
|
|
2755
|
+
/**
|
|
2756
|
+
* Ask the server to open the operating system's folder window.
|
|
2757
|
+
*
|
|
2758
|
+
* The browser's own picker never reveals an absolute path, so a working
|
|
2759
|
+
* directory has to be chosen on the machine that runs the agent. Resolves to the
|
|
2760
|
+
* chosen path, "" when the user closed the window, or null when this browser
|
|
2761
|
+
* cannot use a system window: another device, or no desktop behind the server.
|
|
2762
|
+
*/
|
|
2763
|
+
async function pickFolderInSystemWindow(start, feedback) {
|
|
2764
|
+
if (feedback) feedback.textContent = tr("Waiting for the folder window on your desktop…");
|
|
2765
|
+
try {
|
|
2766
|
+
const response = await apiFetch("/api/fs/pick", {
|
|
2767
|
+
method: "POST",
|
|
2768
|
+
headers: { "Content-Type": "application/json" },
|
|
2769
|
+
body: JSON.stringify({ start, title: tr("Select a working directory") }),
|
|
2770
|
+
});
|
|
2771
|
+
const data = await response.json().catch(() => ({}));
|
|
2772
|
+
if (!response.ok) return null;
|
|
2773
|
+
return typeof data.path === "string" ? data.path : "";
|
|
2774
|
+
} catch {
|
|
2775
|
+
// A window that could not open is a reason to fall back, never an error.
|
|
2776
|
+
return null;
|
|
2777
|
+
} finally {
|
|
2778
|
+
if (feedback) feedback.textContent = "";
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
/**
|
|
2783
|
+
* Choose a working directory: the system folder window first, the in-page
|
|
2784
|
+
* folder browser when no window can open here.
|
|
2785
|
+
*/
|
|
2786
|
+
async function chooseFolder({ start = "", onPick = null, feedback = null } = {}) {
|
|
2787
|
+
const picked = await pickFolderInSystemWindow(start, feedback);
|
|
2788
|
+
if (picked === null) {
|
|
2789
|
+
openFolderDialog({ start, onPick });
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
if (picked && typeof onPick === "function") onPick(picked);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2692
2795
|
function openFolderDialog({ start = "", onPick = null } = {}) {
|
|
2693
2796
|
state.folder = {
|
|
2694
2797
|
path: "",
|
|
@@ -2792,12 +2895,28 @@ function planStatusLabel(status) {
|
|
|
2792
2895
|
return tr("Pending");
|
|
2793
2896
|
}
|
|
2794
2897
|
|
|
2898
|
+
function fastPhaseLabel(phase) {
|
|
2899
|
+
switch (phase) {
|
|
2900
|
+
case "planning": return tr("Planning tasks");
|
|
2901
|
+
case "executing": return tr("Running tasks concurrently");
|
|
2902
|
+
case "verifying": return tr("Verifying local results");
|
|
2903
|
+
case "integrating": return tr("Integrating results");
|
|
2904
|
+
case "reviewing": return tr("Final global review");
|
|
2905
|
+
case "completed": return tr("Fast Mode completed");
|
|
2906
|
+
case "cancelled": return tr("Fast Mode cancelled");
|
|
2907
|
+
case "cancelling": return tr("Stopping tasks…");
|
|
2908
|
+
default: return tr("Fast Mode failed");
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2795
2912
|
function renderPlanCard(source) {
|
|
2796
2913
|
if (!el.planCard) return;
|
|
2797
2914
|
const plan = source && source.plan && Array.isArray(source.plan.steps) && source.plan.steps.length ? source.plan : null;
|
|
2798
2915
|
const tokens = source && source.tokens ? source.tokens : null;
|
|
2799
2916
|
const hasUsage = tokens && (Number(tokens.prompt) > 0 || Number(tokens.completion) > 0);
|
|
2800
|
-
|
|
2917
|
+
const fast = (source?.activeAgentMode === "fast_dag" || (!source?.activeAgentMode && source?.agent_mode === "fast_dag"))
|
|
2918
|
+
? source.fast || source.fast_run : null;
|
|
2919
|
+
if (!state.planOpen || (!plan && !hasUsage && !fast)) {
|
|
2801
2920
|
el.planCard.classList.add("hidden");
|
|
2802
2921
|
el.planCard.replaceChildren();
|
|
2803
2922
|
return;
|
|
@@ -2822,6 +2941,39 @@ function renderPlanCard(source) {
|
|
|
2822
2941
|
head.append(title, close);
|
|
2823
2942
|
card.appendChild(head);
|
|
2824
2943
|
|
|
2944
|
+
if (fast) {
|
|
2945
|
+
const progress = document.createElement("div");
|
|
2946
|
+
progress.className = "fast-progress";
|
|
2947
|
+
progress.setAttribute("role", "status");
|
|
2948
|
+
progress.setAttribute("aria-live", "polite");
|
|
2949
|
+
const phase = document.createElement("strong");
|
|
2950
|
+
phase.textContent = tr("Fast Mode") + " · " + fastPhaseLabel(fast.phase);
|
|
2951
|
+
const tasks = (fast.tasks || []).filter(task => !task.verifies);
|
|
2952
|
+
const count = document.createElement("p");
|
|
2953
|
+
count.textContent = tr("{running} running · {verified}/{total} verified", {
|
|
2954
|
+
running: (fast.tasks || []).filter(task => task.status === "RUNNING" || (task.kind === "verification" && task.status === "VERIFYING")).length,
|
|
2955
|
+
verified: tasks.filter(task => task.status === "COMPLETED").length, total: tasks.length,
|
|
2956
|
+
});
|
|
2957
|
+
progress.append(phase, count);
|
|
2958
|
+
const list = document.createElement("ul");
|
|
2959
|
+
for (const task of tasks) {
|
|
2960
|
+
const row = document.createElement("li");
|
|
2961
|
+
row.textContent = `${task.task_goal} · ${task.status}`;
|
|
2962
|
+
list.appendChild(row);
|
|
2963
|
+
}
|
|
2964
|
+
progress.appendChild(list);
|
|
2965
|
+
if (fast.error) {
|
|
2966
|
+
const error = document.createElement("p");
|
|
2967
|
+
error.className = "error"; error.textContent = fast.error;
|
|
2968
|
+
const fallback = document.createElement("button");
|
|
2969
|
+
fallback.type = "button"; fallback.className = "control";
|
|
2970
|
+
fallback.textContent = tr("Select Standard mode to continue");
|
|
2971
|
+
fallback.addEventListener("click", () => { agentModePending = chooseAgentMode("standard"); void agentModePending.catch(() => {}); });
|
|
2972
|
+
progress.append(error, fallback);
|
|
2973
|
+
}
|
|
2974
|
+
card.appendChild(progress);
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2825
2977
|
if (plan) {
|
|
2826
2978
|
if (plan.goal) {
|
|
2827
2979
|
const goal = document.createElement("div");
|
|
@@ -3347,9 +3499,12 @@ el.back.addEventListener("click", () => setConversationView(false));
|
|
|
3347
3499
|
el.send.addEventListener("click", activateComposer);
|
|
3348
3500
|
el.input.addEventListener("input", autosize);
|
|
3349
3501
|
el.input.addEventListener("keydown", (event) => {
|
|
3502
|
+
// An Enter that picks an IME candidate belongs to the input method, not to us:
|
|
3503
|
+
// preventDefault would cancel the commit and send the uncommitted composition.
|
|
3504
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
3350
3505
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
3351
3506
|
event.preventDefault();
|
|
3352
|
-
sendMessage();
|
|
3507
|
+
sendMessage(null, el.input.value);
|
|
3353
3508
|
}
|
|
3354
3509
|
});
|
|
3355
3510
|
|
|
@@ -3363,8 +3518,20 @@ el.workspaceAll.addEventListener("click", () => {
|
|
|
3363
3518
|
showHome();
|
|
3364
3519
|
});
|
|
3365
3520
|
el.workspaceAdd.addEventListener("click", () => {
|
|
3366
|
-
|
|
3367
|
-
|
|
3521
|
+
// The window opens on the desktop of the machine running the console, so this
|
|
3522
|
+
// sheet stays open with a line saying where to look, and closes once a folder
|
|
3523
|
+
// has been chosen (or when the in-page browser takes over).
|
|
3524
|
+
const start = state.workspace?.path || state.workspaces?.cwd || "";
|
|
3525
|
+
void pickFolderInSystemWindow(start, el.workspaceFeedback).then((picked) => {
|
|
3526
|
+
if (picked === null) {
|
|
3527
|
+
el.workspaceDialog.close();
|
|
3528
|
+
openFolderDialog({ start, onPick: (path) => openWorkspace(path) });
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
if (!picked) return;
|
|
3532
|
+
el.workspaceDialog.close();
|
|
3533
|
+
openWorkspace(picked);
|
|
3534
|
+
});
|
|
3368
3535
|
});
|
|
3369
3536
|
el.agentsOpen.addEventListener("click", () => openAgentsDialog());
|
|
3370
3537
|
el.agentMigrate.addEventListener("click", () => migrateSelectedAgentSession());
|
|
@@ -3386,8 +3553,9 @@ el.newChatForm.addEventListener("submit", (event) => {
|
|
|
3386
3553
|
submitNewChat();
|
|
3387
3554
|
});
|
|
3388
3555
|
el.newChatBrowse.addEventListener("click", () => {
|
|
3389
|
-
|
|
3556
|
+
void chooseFolder({
|
|
3390
3557
|
start: el.newChatCwd.value.trim() || state.workspace?.path || "",
|
|
3558
|
+
feedback: el.newChatFeedback,
|
|
3391
3559
|
onPick: (path) => {
|
|
3392
3560
|
el.newChatCwd.value = path;
|
|
3393
3561
|
renderNewChatRecents();
|
|
@@ -3475,3 +3643,24 @@ loadSeen().then(() => loadOverview()).then(async () => {
|
|
|
3475
3643
|
if (state.workspace && !state.selection) renderWorkspaceLanding(state.workspace.path);
|
|
3476
3644
|
});
|
|
3477
3645
|
setInterval(() => loadOverview({ silent: true }), REFRESH_MS);
|
|
3646
|
+
|
|
3647
|
+
// The plugin shell consumes the same public conversation projection as the built-in UI.
|
|
3648
|
+
if (typeof createPluginUI === "function") pluginUI = createPluginUI({
|
|
3649
|
+
profile: PRO_MODE ? "pro" : "standard",
|
|
3650
|
+
api: apiFetch,
|
|
3651
|
+
context: () => {
|
|
3652
|
+
const data = state.snapshot ?? state.fileData ?? state.agentData ?? {};
|
|
3653
|
+
const selection = state.selection;
|
|
3654
|
+
return { identity:selection ? `${selection.kind}:${selection.key ?? selection.id ?? selection.cwd ?? ""}` : "",
|
|
3655
|
+
cwd:data.cwd ?? selection?.cwd ?? state.workspace?.path ?? "",messages:data.messages ?? [],
|
|
3656
|
+
plan:data.plan,model:data.model,streamingContent:data.streamingContent,pluginUI:data.task?.pluginUI,pluginPrompt:data.task?.pluginPrompt,pluginErrors:data.task?.pluginErrors };
|
|
3657
|
+
},
|
|
3658
|
+
getDraft: () => el.input.value,
|
|
3659
|
+
setDraft: text => { el.input.value=text; autosize(); },
|
|
3660
|
+
submit: async () => { if (!state.selection) throw new Error("Open a conversation first"); await sendMessage({ selection:state.selection,text:el.input.value }); },
|
|
3661
|
+
command: async (name,args) => {
|
|
3662
|
+
if (!state.selection) throw new Error("Open a conversation first");
|
|
3663
|
+
await sendMessage({ selection:state.selection,text:`/plugin ${name} ${JSON.stringify(args)}`,preserveDraft:true });
|
|
3664
|
+
},
|
|
3665
|
+
notice: message => { el.sub.textContent=message; },
|
|
3666
|
+
});
|
package/web/i18n.js
CHANGED
|
@@ -81,11 +81,24 @@ export function locale() {
|
|
|
81
81
|
const ZH = {
|
|
82
82
|
"Agent mode": "Agent 模式",
|
|
83
83
|
"Standard mode": "标准模式",
|
|
84
|
-
"PTC mode": "PTC模式",
|
|
84
|
+
"PTC mode": "PTC 模式",
|
|
85
85
|
"Fast mode": "快速模式",
|
|
86
|
+
"Fast Mode": "极速模式",
|
|
87
|
+
"Fast Mode is disabled; select another mode.": "极速模式已关闭,请选择其他模式。",
|
|
88
|
+
"Dynamic task scheduling and concurrent execution shorten complex tasks while preserving reasoning capability.": "通过动态任务调度与多任务并发执行,大幅缩短复杂任务的完成时间,同时保持原有推理能力。",
|
|
89
|
+
"Planning tasks": "正在规划任务",
|
|
90
|
+
"Running tasks concurrently": "正在并发执行",
|
|
91
|
+
"Verifying local results": "正在执行局部验证",
|
|
92
|
+
"Integrating results": "正在整合结果",
|
|
93
|
+
"Final global review": "正在进行最终审查",
|
|
94
|
+
"Fast Mode completed": "极速模式已完成",
|
|
95
|
+
"Fast Mode failed": "极速模式执行失败",
|
|
96
|
+
"Fast Mode cancelled": "极速模式已取消",
|
|
97
|
+
"{running} running · {verified}/{total} verified": "{running} 个正在运行 · {verified}/{total} 个已验证",
|
|
98
|
+
"Select Standard mode to continue": "选择标准模式继续",
|
|
86
99
|
"Professional mode": "专业模式",
|
|
87
|
-
"Full-featured Agent for every scenario": "功能完整的Agent,适用于所有场景",
|
|
88
|
-
"All standard capabilities, with code-based planning to save tokens": "拥有标准模式所有功能,通过代码实现规划,节省Token",
|
|
100
|
+
"Full-featured Agent for every scenario": "功能完整的 Agent ,适用于所有场景",
|
|
101
|
+
"All standard capabilities, with code-based planning to save tokens": "拥有标准模式所有功能,通过代码实现规划,节省 Token ",
|
|
89
102
|
"Less thinking and fewer calls for quick answers": "减少思考和调用,迅速回答问题",
|
|
90
103
|
"Assign tasks to the best-suited models so multiple models complement each other": "适合需要多个模型取长补短的任务,将任务分给最适合他的模型",
|
|
91
104
|
"Applies to the next turn": "从下一轮开始生效",
|
|
@@ -180,6 +193,8 @@ const ZH = {
|
|
|
180
193
|
"Open folder…": "打开文件夹…",
|
|
181
194
|
"Open working directory": "打开工作目录",
|
|
182
195
|
"Choose a folder to open as the working directory.": "选择一个文件夹作为工作目录。",
|
|
196
|
+
"Select a working directory": "选择工作目录",
|
|
197
|
+
"Waiting for the folder window on your desktop…": "正在等待桌面上的文件夹窗口…",
|
|
183
198
|
"This folder no longer exists.": "此文件夹已不存在。",
|
|
184
199
|
"{n} conversations": "{n} 个对话",
|
|
185
200
|
"Recent folders": "最近文件夹",
|
package/web/index.html
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<title>Neutrino</title>
|
|
26
26
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f9fafb' stroke-width='1.7'%3E%3Cellipse cx='12' cy='12' rx='9.4' ry='3.8' transform='rotate(-30 12 12)'/%3E%3Ccircle cx='12' cy='12' r='3.1'/%3E%3C/svg%3E" />
|
|
27
27
|
<link rel="stylesheet" href="style.css" />
|
|
28
|
+
<link rel="stylesheet" href="plugins.css" />
|
|
28
29
|
</head>
|
|
29
30
|
<body>
|
|
30
31
|
<div id="app">
|
|
@@ -135,6 +136,7 @@
|
|
|
135
136
|
<button type="button" role="menuitemradio" aria-checked="true" data-agent-mode="standard"><span><strong data-i18n="Standard mode">Standard mode</strong><small data-i18n="Full-featured Agent for every scenario">Full-featured Agent for every scenario</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
136
137
|
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="ptc"><span><strong data-i18n="PTC mode">PTC mode</strong><small data-i18n="All standard capabilities, with code-based planning to save tokens">All standard capabilities, with code-based planning to save tokens</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
137
138
|
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="fast"><span><strong data-i18n="Fast mode">Fast mode</strong><small data-i18n="Less thinking and fewer calls for quick answers">Less thinking and fewer calls for quick answers</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
139
|
+
<button class="fast-mode-card" type="button" role="menuitemradio" aria-checked="false" data-agent-mode="fast_dag"><span><strong data-i18n="Fast Mode">Fast Mode</strong><small data-i18n="Dynamic task scheduling and concurrent execution shorten complex tasks while preserving reasoning capability.">Dynamic task scheduling and concurrent execution shorten complex tasks while preserving reasoning capability.</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
138
140
|
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="pro"><span><strong data-i18n="Professional mode">Professional mode</strong><small data-i18n="Assign tasks to the best-suited models so multiple models complement each other">Assign tasks to the best-suited models so multiple models complement each other</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
139
141
|
</div>
|
|
140
142
|
<p id="agent-mode-feedback" class="agent-mode-feedback" role="status" aria-live="polite"></p>
|
|
@@ -182,7 +184,7 @@
|
|
|
182
184
|
</div>
|
|
183
185
|
</details>
|
|
184
186
|
<button id="send" class="primary" type="button" title="Send message" aria-label="Send message" data-i18n-title="Send message" data-i18n-aria-label="Send message">
|
|
185
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path class="send-icon" d="M12 19V5M5 12l7-7 7 7" /><rect class="pause-icon" x="5" y="5" width="14" height="14" rx="2" fill="currentColor" stroke="none" /></svg>
|
|
187
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path class="send-icon" d="M12 19V5M5 12l7-7 7 7" /><rect class="pause-icon" x="5" y="5" width="14" height="14" rx="2" fill="currentColor" stroke="none" /><path class="resume-icon" d="M8 5.6v12.8L19 12z" fill="currentColor" stroke="none" /></svg>
|
|
186
188
|
</button>
|
|
187
189
|
</div>
|
|
188
190
|
<div class="composer-hint">
|
package/web/plugins.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
.plugin-manager-body{display:flex;flex-direction:column;gap:14px}.plugin-dialog{width:min(860px,94vw)}
|
|
2
|
+
.plugin-list{display:grid;gap:12px}.plugin-card{padding:16px;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface-1)}
|
|
3
|
+
.plugin-card-heading,.plugin-actions,.plugin-install{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.plugin-card-heading{justify-content:space-between}.plugin-card code{font-size:12px;color:var(--text-caption)}
|
|
4
|
+
.plugin-card p{margin:9px 0}.plugin-badge{font-size:11px;color:var(--accent);border:1px solid var(--accent-line);border-radius:20px;padding:3px 8px}.plugin-warning{color:var(--amber);font-size:13px}
|
|
5
|
+
.plugin-config,.plugin-providers{display:grid;gap:10px;margin-top:12px}.plugin-field{display:flex;justify-content:space-between;align-items:center;gap:12px;font-size:13px}.plugin-field>span{flex:1}.plugin-field select,.plugin-field input:not([type=checkbox]){max-width:55%;min-width:140px}.plugin-field textarea{max-width:60%;min-height:90px;color:var(--text);background:var(--surface-code);border:1px solid var(--border)}
|
|
6
|
+
.plugin-install input{flex:1}.plugin-details{white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;max-height:280px;overflow:auto}.plugin-card summary{cursor:pointer;font-size:13px;margin-top:12px}
|
|
7
|
+
.plugin-frame{width:100%;display:block;border:0;min-height:40px;background:transparent}.plugin-tabs,.plugin-toolbar,.plugin-header-actions,.plugin-menu{display:flex;flex-wrap:wrap;gap:7px}.plugin-tabs:empty,.plugin-toolbar:empty,.plugin-menu:empty{display:none}.plugin-tabs{padding:8px 22px}.plugin-tabs .control{font-size:12px}.plugin-toolbar{padding:0 0 7px}.plugin-sidebar{padding:8px 16px}.plugin-sidebar:empty{display:none}
|
|
8
|
+
.plugin-panel{margin:8px 22px;padding:12px;background:var(--surface-1);border:1px solid var(--border);border-radius:var(--radius-md)}.plugin-panel-content{margin-top:10px}.plugin-skill-select{max-width:260px;font-size:12px}.plugin-composer-target{width:100%}
|
|
9
|
+
.composer-card{border-radius:var(--neutrino-composer-radius,var(--radius-xl))}.message{border-radius:var(--neutrino-message-radius,0)}
|
|
10
|
+
@media(max-width:640px){.plugin-field{align-items:flex-start;flex-direction:column}.plugin-field input:not([type=checkbox]),.plugin-field select,.plugin-field textarea{max-width:100%;width:100%}.plugin-panel{margin:6px}.plugin-actions .control{font-size:12px}}
|