@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/dist/web/server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { handlePlugins } from "./plugins.js";
|
|
2
|
+
import { startLocalApi } from "./localApi.js";
|
|
1
3
|
/**
|
|
2
4
|
* A tiny, dependency-free HTTP server that exposes Neutrino to a browser.
|
|
3
5
|
*
|
|
@@ -19,8 +21,10 @@ import { timingSafeEqual } from "node:crypto";
|
|
|
19
21
|
import { fileURLToPath } from "node:url";
|
|
20
22
|
import { deleteSession, listSessions, loadConfig, normalizeApprovalMode, isAgentMode, resolveModelSelection, saveConfig, sessionDir, } from "../config.js";
|
|
21
23
|
import { getWebSettings, updateWebSettings } from "./settings.js";
|
|
24
|
+
import { fastSettings } from "../fast/types.js";
|
|
22
25
|
import { isSeenIdentity, loadSeen, updateSeen, SEEN_LIMIT } from "./clientState.js";
|
|
23
26
|
import { WebAuth } from "./auth.js";
|
|
27
|
+
import { pickFolder } from "./folderPicker.js";
|
|
24
28
|
import { normalizeCwd, sameCwd } from "../cwd.js";
|
|
25
29
|
import { hub } from "./hub.js";
|
|
26
30
|
import { writeCrashLog } from "../terminal/restore.js";
|
|
@@ -451,6 +455,7 @@ function buildOverview(mode = "standard") {
|
|
|
451
455
|
.sort((a, b) => (a.updated_at < b.updated_at ? 1 : a.updated_at > b.updated_at ? -1 : 0));
|
|
452
456
|
return {
|
|
453
457
|
groups: ordered,
|
|
458
|
+
fast_mode_enabled: fastSettings(loadConfig("standard")).enabled,
|
|
454
459
|
live: lives,
|
|
455
460
|
generated_at: new Date().toISOString(),
|
|
456
461
|
};
|
|
@@ -513,6 +518,14 @@ function isSameOrigin(req) {
|
|
|
513
518
|
return false;
|
|
514
519
|
}
|
|
515
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* True when the request came from this machine. The system folder window is a
|
|
523
|
+
* desktop window of the server, so only a browser beside it may ask for one.
|
|
524
|
+
*/
|
|
525
|
+
function isLoopbackRequest(req) {
|
|
526
|
+
const address = String(req.socket?.remoteAddress ?? "").replace(/^::ffff:/i, "");
|
|
527
|
+
return address === "::1" || address.startsWith("127.");
|
|
528
|
+
}
|
|
516
529
|
/** Constant-time comparison of the service's management token. */
|
|
517
530
|
function hasServiceToken(req, token) {
|
|
518
531
|
const header = String(req.headers.authorization ?? "");
|
|
@@ -688,7 +701,7 @@ function streamUpdateEvents(req, res, streams) {
|
|
|
688
701
|
req.on("error", cleanup);
|
|
689
702
|
res.on("error", cleanup);
|
|
690
703
|
}
|
|
691
|
-
async function handleApi(req, res, url, auth, mode, pathname, binding, updates) {
|
|
704
|
+
async function handleApi(req, res, url, auth, mode, pathname, binding, updates, picker) {
|
|
692
705
|
if (!pathname.startsWith("/api/"))
|
|
693
706
|
return false;
|
|
694
707
|
// The Pro presets are not bundled either: the first Pro request makes sure they
|
|
@@ -789,6 +802,10 @@ async function handleApi(req, res, url, auth, mode, pathname, binding, updates)
|
|
|
789
802
|
sendJson(res, 401, { error: "Password login required." });
|
|
790
803
|
return true;
|
|
791
804
|
}
|
|
805
|
+
if (await handlePlugins(req, res, url, pathname, mode, {
|
|
806
|
+
sameOrigin: isSameOrigin(req), read: () => readJsonBody(req), json: (status, value) => sendJson(res, status, value),
|
|
807
|
+
}))
|
|
808
|
+
return true;
|
|
792
809
|
// The first browser to see the background-residency prompt consumes it for
|
|
793
810
|
// this console run, whether it accepted or dismissed it.
|
|
794
811
|
if (pathname === "/api/background/dismiss") {
|
|
@@ -1011,6 +1028,53 @@ async function handleApi(req, res, url, auth, mode, pathname, binding, updates)
|
|
|
1011
1028
|
}
|
|
1012
1029
|
return true;
|
|
1013
1030
|
}
|
|
1031
|
+
// The working directory has to be chosen where the agent runs: a browser's
|
|
1032
|
+
// own picker only exposes a folder's contents, never its absolute path. The
|
|
1033
|
+
// page asks for the operating system's window instead, drawn here on the
|
|
1034
|
+
// desktop. A browser on another device gets no window at all — it would open
|
|
1035
|
+
// on the wrong desk — and keeps the in-page folder browser.
|
|
1036
|
+
if (pathname === "/api/fs/pick") {
|
|
1037
|
+
if (req.method !== "POST") {
|
|
1038
|
+
sendJson(res, 405, { error: "method not allowed" });
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1041
|
+
if (!isSameOrigin(req)) {
|
|
1042
|
+
sendJson(res, 403, { error: "cross-origin request rejected" });
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
if (!isLoopbackRequest(req)) {
|
|
1046
|
+
sendJson(res, 501, { error: "the system folder window opens on this machine only", code: "native picker unavailable" });
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
let body;
|
|
1050
|
+
try {
|
|
1051
|
+
body = await readJsonBody(req, 4096);
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
sendApiError(res, err);
|
|
1055
|
+
return true;
|
|
1056
|
+
}
|
|
1057
|
+
const request = (body ?? {});
|
|
1058
|
+
if (typeof request !== "object" || Array.isArray(request)
|
|
1059
|
+
|| (request.start !== undefined && typeof request.start !== "string")
|
|
1060
|
+
|| (request.title !== undefined && typeof request.title !== "string")) {
|
|
1061
|
+
sendJson(res, 400, { error: "invalid request" });
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
try {
|
|
1065
|
+
const outcome = await picker({ start: request.start, title: request.title });
|
|
1066
|
+
if ("picked" in outcome)
|
|
1067
|
+
sendJson(res, 200, { path: outcome.picked });
|
|
1068
|
+
else if ("cancelled" in outcome)
|
|
1069
|
+
sendJson(res, 200, { path: null });
|
|
1070
|
+
else
|
|
1071
|
+
sendJson(res, 501, { error: outcome.unavailable, code: "native picker unavailable" });
|
|
1072
|
+
}
|
|
1073
|
+
catch (err) {
|
|
1074
|
+
sendApiError(res, err);
|
|
1075
|
+
}
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1014
1078
|
// Start a brand-new conversation in a working directory chosen by the
|
|
1015
1079
|
// browser. Unlike `/continue`, there is no transcript to load first.
|
|
1016
1080
|
if (pathname === "/api/conversations") {
|
|
@@ -1462,7 +1526,16 @@ async function handleApi(req, res, url, auth, mode, pathname, binding, updates)
|
|
|
1462
1526
|
sendJson(res, 400, { error: "empty message" });
|
|
1463
1527
|
return true;
|
|
1464
1528
|
}
|
|
1465
|
-
|
|
1529
|
+
const agentMode = body?.agent_mode;
|
|
1530
|
+
if (agentMode !== undefined && (!isAgentMode(agentMode) || mode === "pro")) {
|
|
1531
|
+
sendJson(res, 400, { error: "invalid agent mode" });
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
const sent = hub.sendConfigured(key, text, agentMode);
|
|
1535
|
+
if (!sent.ok) {
|
|
1536
|
+
sendJson(res, 409, { error: sent.error });
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1466
1539
|
sendJson(res, 200, { ok: true });
|
|
1467
1540
|
return true;
|
|
1468
1541
|
}
|
|
@@ -1472,7 +1545,7 @@ async function handleApi(req, res, url, auth, mode, pathname, binding, updates)
|
|
|
1472
1545
|
sendJson(res, 404, { error: "not found" });
|
|
1473
1546
|
return true;
|
|
1474
1547
|
}
|
|
1475
|
-
async function handleRequest(req, res, auth, binding, updates) {
|
|
1548
|
+
async function handleRequest(req, res, auth, binding, updates, picker) {
|
|
1476
1549
|
let url;
|
|
1477
1550
|
try {
|
|
1478
1551
|
url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -1505,7 +1578,7 @@ async function handleRequest(req, res, auth, binding, updates) {
|
|
|
1505
1578
|
mode = "pro";
|
|
1506
1579
|
apiPathname = pathname.slice("/pro".length);
|
|
1507
1580
|
}
|
|
1508
|
-
if (await handleApi(req, res, url, auth, mode, apiPathname, binding, updates))
|
|
1581
|
+
if (await handleApi(req, res, url, auth, mode, apiPathname, binding, updates, picker))
|
|
1509
1582
|
return;
|
|
1510
1583
|
if (mode === "pro") {
|
|
1511
1584
|
// Nothing else lives under /pro but the page and its API.
|
|
@@ -1535,11 +1608,22 @@ export async function startWebServer(options) {
|
|
|
1535
1608
|
service: options.backgroundService,
|
|
1536
1609
|
updateSettings: (body, mode) => updateSettings(body, mode),
|
|
1537
1610
|
};
|
|
1611
|
+
// One system folder window per console: a second browser asking while it is
|
|
1612
|
+
// open waits for the same window instead of stacking another on the desk.
|
|
1613
|
+
let folderWindow = null;
|
|
1614
|
+
const openFolderWindow = (request) => {
|
|
1615
|
+
if (!folderWindow) {
|
|
1616
|
+
const open = options.folderPicker ?? pickFolder;
|
|
1617
|
+
folderWindow = open(request).finally(() => { folderWindow = null; });
|
|
1618
|
+
}
|
|
1619
|
+
return folderWindow;
|
|
1620
|
+
};
|
|
1538
1621
|
// Every window watching for an update belongs to this console, not the module,
|
|
1539
1622
|
// so a second embedded server (a test, an embedding) keeps its own set.
|
|
1540
1623
|
const updates = createUpdateStreams();
|
|
1624
|
+
let localApi = null;
|
|
1541
1625
|
const server = http.createServer((req, res) => {
|
|
1542
|
-
handleRequest(req, res, auth, binding, updates).catch((err) => {
|
|
1626
|
+
handleRequest(req, res, auth, binding, updates, openFolderWindow).catch((err) => {
|
|
1543
1627
|
if (!res.headersSent) {
|
|
1544
1628
|
sendJson(res, 500, { error: err instanceof Error ? err.message : "internal error" });
|
|
1545
1629
|
}
|
|
@@ -1678,25 +1762,49 @@ export async function startWebServer(options) {
|
|
|
1678
1762
|
url: "",
|
|
1679
1763
|
urls: [],
|
|
1680
1764
|
setHost: (next) => rebind(next),
|
|
1681
|
-
close:
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1765
|
+
close: async () => {
|
|
1766
|
+
await localApi?.close();
|
|
1767
|
+
localApi = null;
|
|
1768
|
+
await new Promise((resolve) => {
|
|
1769
|
+
closed = true;
|
|
1770
|
+
// These streams never end on their own, so they are closed explicitly;
|
|
1771
|
+
// otherwise server.close() would wait for a window that stays open.
|
|
1772
|
+
updates.closeAll();
|
|
1773
|
+
for (const key of [...webSessions.keys()])
|
|
1774
|
+
closeWebSession(key);
|
|
1775
|
+
if (!server.listening) {
|
|
1776
|
+
resolve();
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
server.close(() => resolve());
|
|
1780
|
+
});
|
|
1781
|
+
},
|
|
1694
1782
|
};
|
|
1695
|
-
|
|
1783
|
+
if (!options.apiOnly)
|
|
1784
|
+
await listenRetry(host);
|
|
1696
1785
|
// Port 0 means "let the OS pick": remember the real one for every URL.
|
|
1697
1786
|
const address = server.address();
|
|
1698
1787
|
port = typeof address === "object" && address ? address.port : options.port;
|
|
1699
1788
|
syncAddress();
|
|
1789
|
+
try {
|
|
1790
|
+
const rawPort = process.env.NEUTRINO_API_PORT;
|
|
1791
|
+
const apiPort = options.apiPort ?? (rawPort === undefined ? (options.port === 0 && !options.apiOnly ? 0 : 8766) : Number(rawPort));
|
|
1792
|
+
if (!Number.isSafeInteger(apiPort) || apiPort < 0 || apiPort > 65535 || (rawPort !== undefined && !/^\d+$/.test(rawPort))) {
|
|
1793
|
+
throw new Error("NEUTRINO_API_PORT must be an integer between 0 and 65535");
|
|
1794
|
+
}
|
|
1795
|
+
const localAuth = new WebAuth(false);
|
|
1796
|
+
localApi = await startLocalApi(apiPort, async (req, res, url, mode, pathname) => {
|
|
1797
|
+
if (!await handleApi(req, res, url, localAuth, mode, pathname, binding, updates, openFolderWindow)) {
|
|
1798
|
+
sendJson(res, 404, { error: "not found" });
|
|
1799
|
+
}
|
|
1800
|
+
}, options.publishApi ?? options.port !== 0);
|
|
1801
|
+
handle.api = { url: localApi.url, token: localApi.token };
|
|
1802
|
+
}
|
|
1803
|
+
catch (error) {
|
|
1804
|
+
if (server.listening)
|
|
1805
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
1806
|
+
throw error;
|
|
1807
|
+
}
|
|
1700
1808
|
// Once listening, the server must tolerate connection-level failures
|
|
1701
1809
|
// (resets, EMFILE, half-open sockets) without the process dying: an 'error'
|
|
1702
1810
|
// event with no listener is fatal in Node.
|
package/dist/web/service.js
CHANGED
|
@@ -120,6 +120,7 @@ export async function runWebService(options) {
|
|
|
120
120
|
try {
|
|
121
121
|
handle = await startWebServer({
|
|
122
122
|
port: options.port, host: options.host, auth: options.auth,
|
|
123
|
+
publishApi: true,
|
|
123
124
|
backgroundPrompt: !resident && process.env.NEUTRINO_BACKGROUND_PROMPT === "1",
|
|
124
125
|
backgroundService: { token, onStop: shutdown, status, configure },
|
|
125
126
|
onAddressChange: () => { if (handle)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { type AgentMode, type ModelInfo, type SessionData, type ResolvedModelSelection, type ModelSelection, type ApprovalMode, type Mode } from "../config.js";
|
|
2
|
+
import { type LiveAgent, type LiveMeta, type LiveSnapshot, type ModelChangeResult } from "./hub.js";
|
|
3
|
+
export type ResumeOutcome = {
|
|
4
|
+
ok: true;
|
|
5
|
+
session: WebSession;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A fresh session id in the same shape `saveSession` generates, so a new
|
|
12
|
+
* browser conversation has a stable identity before its first message exists.
|
|
13
|
+
*/
|
|
14
|
+
export declare function newSessionId(now?: Date): string;
|
|
15
|
+
/**
|
|
16
|
+
* Start an empty conversation in a caller-chosen working directory. Nothing is
|
|
17
|
+
* written to disk until the first message, so an abandoned new-chat dialog
|
|
18
|
+
* cannot litter the history with empty transcripts.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createWebSession(cwd: string, selection: ResolvedModelSelection, approvalMode?: ApprovalMode, mode?: Mode, proPreset?: string, agentMode?: AgentMode): WebSession;
|
|
21
|
+
/** A preset may need fallback; defer model credentials/availability to preflight. */
|
|
22
|
+
export declare function proInitialSelection(preset?: string): ResolvedModelSelection;
|
|
23
|
+
/**
|
|
24
|
+
* Look up the model recorded in the transcript. Returns a readable error
|
|
25
|
+
* instead of throwing, so the HTTP layer can answer with a clean 4xx.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveModel(modelName: string, providerName: string): {
|
|
28
|
+
ok: true;
|
|
29
|
+
model: ModelInfo;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
error: string;
|
|
33
|
+
};
|
|
34
|
+
/** Build a web session from a saved transcript, or explain why we cannot. */
|
|
35
|
+
export declare function resumeSession(source: SessionData, selection?: ModelSelection, mode?: Mode): ResumeOutcome;
|
|
36
|
+
export declare class WebSession implements LiveAgent {
|
|
37
|
+
key: string | null;
|
|
38
|
+
/** Workspace this conversation belongs to; keeps Pro and standard separate. */
|
|
39
|
+
readonly mode: Mode;
|
|
40
|
+
private model;
|
|
41
|
+
private provider;
|
|
42
|
+
private intensity;
|
|
43
|
+
private readonly dir;
|
|
44
|
+
private readonly tokens;
|
|
45
|
+
private messages;
|
|
46
|
+
private sessionId;
|
|
47
|
+
private busy;
|
|
48
|
+
private approvalMode;
|
|
49
|
+
private paused;
|
|
50
|
+
private queued;
|
|
51
|
+
private compressing;
|
|
52
|
+
private context;
|
|
53
|
+
private streamingContent;
|
|
54
|
+
/** The plan of the current turn, if this turn was planned. */
|
|
55
|
+
private plan;
|
|
56
|
+
/** True while the planner agent is writing the steps. */
|
|
57
|
+
private planning;
|
|
58
|
+
private proPreset;
|
|
59
|
+
private proTeam;
|
|
60
|
+
private proState;
|
|
61
|
+
private proAbort;
|
|
62
|
+
/** What the sub-agent is doing, or null when none is running. */
|
|
63
|
+
private subagent;
|
|
64
|
+
private saveError;
|
|
65
|
+
/** A retried request, so the browser can explain why the stream is quiet. */
|
|
66
|
+
private retrying;
|
|
67
|
+
private checkpoint;
|
|
68
|
+
private taskRuntime;
|
|
69
|
+
private lastTask;
|
|
70
|
+
private taskOutcome;
|
|
71
|
+
private turnToken;
|
|
72
|
+
/** Set by a resume click: the next turn clears its tool calls without asking. */
|
|
73
|
+
private autoApproveNextTurn;
|
|
74
|
+
private autoApproveToken;
|
|
75
|
+
private agentMode;
|
|
76
|
+
private activeAgentMode;
|
|
77
|
+
private fastState?;
|
|
78
|
+
private queuedAgentMode;
|
|
79
|
+
private turnAbort;
|
|
80
|
+
private lastPromptTokens;
|
|
81
|
+
private disposed;
|
|
82
|
+
private idleTimer;
|
|
83
|
+
/** Non-null while a tool call waits for the browser to answer. */
|
|
84
|
+
private approvalQueue;
|
|
85
|
+
private pendingApproval;
|
|
86
|
+
/** Reviewer count while an automated review runs, null otherwise. */
|
|
87
|
+
private reviewing;
|
|
88
|
+
constructor(source: SessionData, model: ModelInfo, mode?: Mode);
|
|
89
|
+
/** Register with the live hub so the browser can watch this conversation. */
|
|
90
|
+
start(): string;
|
|
91
|
+
/** Detach from the hub. The transcript stays on disk. */
|
|
92
|
+
dispose(): void;
|
|
93
|
+
liveCwd(): string;
|
|
94
|
+
liveConfigure(selection: ResolvedModelSelection): ModelChangeResult;
|
|
95
|
+
liveSetProPreset(preset: string): ModelChangeResult;
|
|
96
|
+
liveMeta(): LiveMeta;
|
|
97
|
+
liveSnapshot(key: string): LiveSnapshot;
|
|
98
|
+
livePause(): void;
|
|
99
|
+
liveResume(): void;
|
|
100
|
+
liveSend(text: string): void;
|
|
101
|
+
liveApprove(approved: boolean, approvalId?: string): boolean;
|
|
102
|
+
liveSetAgentMode(mode: AgentMode): ModelChangeResult;
|
|
103
|
+
liveSetApprovalMode(mode: ApprovalMode): void;
|
|
104
|
+
/**
|
|
105
|
+
* Start a fresh chat in the same working directory, mirroring the terminal's
|
|
106
|
+
* clear shortcut. The finished transcript is checkpointed first and stays on
|
|
107
|
+
* disk; this session simply moves on under a new id.
|
|
108
|
+
*/
|
|
109
|
+
liveClear(): void;
|
|
110
|
+
private send;
|
|
111
|
+
private maybeFlushQueue;
|
|
112
|
+
private startTurn;
|
|
113
|
+
/**
|
|
114
|
+
* One user turn, mirroring the terminal: plan first, then execute the steps in
|
|
115
|
+
* order, one agent loop per step. Without a usable plan the turn runs in one
|
|
116
|
+
* pass, exactly as it did before planning existed.
|
|
117
|
+
*/
|
|
118
|
+
private runTurn;
|
|
119
|
+
private runTaskTurn;
|
|
120
|
+
/** Independent engine; Standard/PTC/legacy Fast retain their original loops. */
|
|
121
|
+
private runFastTurn;
|
|
122
|
+
/** Freeze one resolved team per task, plan first, then give the whole brief to main. */
|
|
123
|
+
private runProTurn;
|
|
124
|
+
/**
|
|
125
|
+
* The transcript this turn may see: the messages it started with plus what it
|
|
126
|
+
* added since. A user message queued while the turn runs belongs to the next
|
|
127
|
+
* turn, so the model never sees it early.
|
|
128
|
+
*/
|
|
129
|
+
private turnView;
|
|
130
|
+
/** Move the plan to one step and publish it. */
|
|
131
|
+
private setStep;
|
|
132
|
+
/**
|
|
133
|
+
* Run the planner agent over the current request. Null on every failure path:
|
|
134
|
+
* a plan must improve a turn, never be a new way for one to fail.
|
|
135
|
+
*/
|
|
136
|
+
private makePlan;
|
|
137
|
+
/** Ends a turn whose floating promise rejected, instead of crashing the app. */
|
|
138
|
+
private failTurn;
|
|
139
|
+
private buildApiMessages;
|
|
140
|
+
/**
|
|
141
|
+
* One agent conversation for a single request (or a single plan step). Returns
|
|
142
|
+
* true when the model finished on its own, false when the turn was cut short;
|
|
143
|
+
* the caller owns the turn's end because a planned turn runs this per step.
|
|
144
|
+
*/
|
|
145
|
+
private agentLoop;
|
|
146
|
+
private onTurnFinished;
|
|
147
|
+
/** Classify a tool call and, when it is risky, wait for a decision. */
|
|
148
|
+
private requestApproval;
|
|
149
|
+
/** Reviewer model: `approval_model` when set, otherwise the chat model. */
|
|
150
|
+
private reviewerModel;
|
|
151
|
+
private runProgram;
|
|
152
|
+
/**
|
|
153
|
+
* Run one approved tool call. `delegate` runs a child agent instead of
|
|
154
|
+
* touching the filesystem, so it is handled here, where the session's model,
|
|
155
|
+
* approval gate and abort signal live.
|
|
156
|
+
*/
|
|
157
|
+
private runTool;
|
|
158
|
+
private dispatchTool;
|
|
159
|
+
/**
|
|
160
|
+
* Run a sub-agent for one `delegate` call and return its report. The child
|
|
161
|
+
* works in its own transcript; only the report reaches this conversation, and
|
|
162
|
+
* its risky calls go through the same approval gate as the parent turn.
|
|
163
|
+
*/
|
|
164
|
+
private delegate;
|
|
165
|
+
private compressThreshold;
|
|
166
|
+
/** Trim old tool results in a long turn without changing the saved history. */
|
|
167
|
+
private trimApiMessages;
|
|
168
|
+
private autoCompress;
|
|
169
|
+
private title;
|
|
170
|
+
private retainStreamingContent;
|
|
171
|
+
private save;
|
|
172
|
+
private writeSession;
|
|
173
|
+
private publishLive;
|
|
174
|
+
private touch;
|
|
175
|
+
private clearIdleTimer;
|
|
176
|
+
}
|