@xingjisoft/neutrino 0.6.2 → 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.
Files changed (166) hide show
  1. package/README.md +6 -1
  2. package/assets/fast-planner.txt +57 -0
  3. package/assets/fast-replanner.txt +28 -0
  4. package/dist/agents.d.ts +25 -0
  5. package/dist/app.d.ts +206 -0
  6. package/dist/app.js +15 -0
  7. package/dist/background.d.ts +96 -0
  8. package/dist/bin.d.ts +2 -0
  9. package/dist/bin.js +40 -3
  10. package/dist/browser.d.ts +18 -0
  11. package/dist/clipboard.d.ts +16 -0
  12. package/dist/colors.d.ts +29 -0
  13. package/dist/compact.d.ts +52 -0
  14. package/dist/config.d.ts +221 -0
  15. package/dist/config.js +19 -7
  16. package/dist/cwd.d.ts +19 -0
  17. package/dist/fast/artifacts.d.ts +26 -0
  18. package/dist/fast/artifacts.js +109 -0
  19. package/dist/fast/engine.d.ts +78 -0
  20. package/dist/fast/engine.js +657 -0
  21. package/dist/fast/evidence.d.ts +21 -0
  22. package/dist/fast/evidence.js +68 -0
  23. package/dist/fast/planner.d.ts +10 -0
  24. package/dist/fast/planner.js +51 -0
  25. package/dist/fast/pool.d.ts +17 -0
  26. package/dist/fast/pool.js +78 -0
  27. package/dist/fast/scheduler.d.ts +54 -0
  28. package/dist/fast/scheduler.js +383 -0
  29. package/dist/fast/types.d.ts +100 -0
  30. package/dist/fast/types.js +21 -0
  31. package/dist/fast/worker.d.ts +23 -0
  32. package/dist/fast/worker.js +185 -0
  33. package/dist/header.d.ts +3 -0
  34. package/dist/index.d.ts +5 -0
  35. package/dist/llm/approval.d.ts +204 -0
  36. package/dist/llm/approval.js +14 -3
  37. package/dist/llm/client.d.ts +118 -0
  38. package/dist/llm/client.js +57 -8
  39. package/dist/llm/codeExecutor.d.ts +14 -0
  40. package/dist/llm/codemode.d.ts +15 -0
  41. package/dist/llm/context.d.ts +29 -0
  42. package/dist/llm/diagnostics.d.ts +91 -0
  43. package/dist/llm/diagnostics.js +242 -0
  44. package/dist/llm/plan.d.ts +148 -0
  45. package/dist/llm/retry.d.ts +80 -0
  46. package/dist/llm/risk.d.ts +21 -0
  47. package/dist/llm/risk.js +7 -0
  48. package/dist/llm/runtime.d.ts +189 -0
  49. package/dist/llm/runtime.js +77 -4
  50. package/dist/llm/search.d.ts +2 -0
  51. package/dist/llm/searchNetwork.d.ts +8 -0
  52. package/dist/llm/subagent.d.ts +112 -0
  53. package/dist/llm/subagent.js +4 -2
  54. package/dist/llm/tools.d.ts +20 -0
  55. package/dist/llm/tools.js +4 -1
  56. package/dist/llm/workspace.d.ts +6 -0
  57. package/dist/llm/workspace.js +271 -0
  58. package/dist/markdown.d.ts +58 -0
  59. package/dist/migration.d.ts +8 -0
  60. package/dist/persistence.d.ts +14 -0
  61. package/dist/plugins/catalog.d.ts +20 -0
  62. package/dist/plugins/catalog.js +45 -0
  63. package/dist/plugins/cli.d.ts +1 -0
  64. package/dist/plugins/cli.js +106 -0
  65. package/dist/plugins/host.d.ts +121 -0
  66. package/dist/plugins/host.js +450 -0
  67. package/dist/plugins/manager.d.ts +24 -0
  68. package/dist/plugins/manager.js +288 -0
  69. package/dist/plugins/package.d.ts +5 -0
  70. package/dist/plugins/package.js +53 -0
  71. package/dist/plugins/runner.d.ts +1 -0
  72. package/dist/plugins/runner.js +227 -0
  73. package/dist/plugins/session.d.ts +12 -0
  74. package/dist/plugins/session.js +97 -0
  75. package/dist/plugins/terminal.d.ts +22 -0
  76. package/dist/plugins/terminal.js +33 -0
  77. package/dist/plugins/types.d.ts +178 -0
  78. package/dist/plugins/types.js +6 -0
  79. package/dist/plugins/validation.d.ts +14 -0
  80. package/dist/plugins/validation.js +174 -0
  81. package/dist/plugins/web-sdk.d.ts +58 -0
  82. package/dist/plugins/web-sdk.js +1 -0
  83. package/dist/pro/agents.d.ts +34 -0
  84. package/dist/pro/config.d.ts +75 -0
  85. package/dist/pro/config.js +3 -2
  86. package/dist/pro/preflight.d.ts +21 -0
  87. package/dist/sanitize.d.ts +34 -0
  88. package/dist/screens/agentConversation.d.ts +28 -0
  89. package/dist/screens/agentHistory.d.ts +25 -0
  90. package/dist/screens/agentSelect.d.ts +16 -0
  91. package/dist/screens/chat.d.ts +292 -0
  92. package/dist/screens/chat.js +139 -21
  93. package/dist/screens/history.d.ts +23 -0
  94. package/dist/screens/modelSelect.d.ts +29 -0
  95. package/dist/screens/plugins.d.ts +29 -0
  96. package/dist/screens/plugins.js +196 -0
  97. package/dist/screens/settings.d.ts +42 -0
  98. package/dist/screens/start.d.ts +43 -0
  99. package/dist/screens/start.js +25 -17
  100. package/dist/screens/updatePrompt.d.ts +41 -0
  101. package/dist/selection.d.ts +75 -0
  102. package/dist/state.d.ts +26 -0
  103. package/dist/terminal/buffer.d.ts +47 -0
  104. package/dist/terminal/input.d.ts +70 -0
  105. package/dist/terminal/renderer.d.ts +43 -0
  106. package/dist/terminal/restore.d.ts +41 -0
  107. package/dist/terminal/screen.d.ts +57 -0
  108. package/dist/tray.d.ts +14 -0
  109. package/dist/ui.d.ts +123 -0
  110. package/dist/update.d.ts +71 -0
  111. package/dist/updateWatch.d.ts +43 -0
  112. package/dist/web/approvalQueue.d.ts +8 -0
  113. package/dist/web/approvalQueue.js +59 -0
  114. package/dist/web/auth.d.ts +14 -0
  115. package/dist/web/clientState.d.ts +16 -0
  116. package/dist/web/folderPicker.d.ts +34 -0
  117. package/dist/web/folderPicker.js +544 -0
  118. package/dist/web/hub.d.ts +213 -0
  119. package/dist/web/hub.js +14 -2
  120. package/dist/web/localApi.d.ts +11 -0
  121. package/dist/web/localApi.js +260 -0
  122. package/dist/web/plugins.d.ts +8 -0
  123. package/dist/web/plugins.js +64 -0
  124. package/dist/web/server.d.ts +81 -0
  125. package/dist/web/server.js +127 -19
  126. package/dist/web/service.d.ts +7 -0
  127. package/dist/web/service.js +1 -0
  128. package/dist/web/session.d.ts +176 -0
  129. package/dist/web/session.js +153 -19
  130. package/dist/web/settings.d.ts +65 -0
  131. package/dist/web/terminalBridge.d.ts +15 -0
  132. package/dist/web/terminalBridge.js +8 -2
  133. package/dist/width.d.ts +6 -0
  134. package/docs/plugins/README.md +126 -0
  135. package/docs/plugins/agent-sdk.md +92 -0
  136. package/docs/plugins/architecture.md +64 -0
  137. package/docs/plugins/compatibility.md +34 -0
  138. package/docs/plugins/development.md +55 -0
  139. package/docs/plugins/manifest.md +59 -0
  140. package/docs/plugins/manifest.schema.json +286 -0
  141. package/docs/plugins/security.md +53 -0
  142. package/docs/plugins/skills-prompts.md +69 -0
  143. package/docs/plugins/ui-sdk.md +109 -0
  144. package/examples/local-api.py +105 -0
  145. package/examples/plugins/cards/card.html +6 -0
  146. package/examples/plugins/cards/neutrino.plugin.json +42 -0
  147. package/examples/plugins/composer/editor.html +10 -0
  148. package/examples/plugins/composer/neutrino.plugin.json +10 -0
  149. package/examples/plugins/function-tools/agent.js +32 -0
  150. package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
  151. package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
  152. package/examples/plugins/menus-tabs/panel.html +6 -0
  153. package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
  154. package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
  155. package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
  156. package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
  157. package/examples/plugins/theme/neutrino.plugin.json +12 -0
  158. package/package.json +66 -42
  159. package/web/LOCAL-API.md +65 -0
  160. package/web/README.md +27 -6
  161. package/web/app.js +201 -32
  162. package/web/i18n.js +15 -0
  163. package/web/index.html +3 -1
  164. package/web/plugins.css +10 -0
  165. package/web/plugins.js +230 -0
  166. package/web/style.css +11 -2
@@ -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
- hub.send(key, text);
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: () => new Promise((resolve) => {
1682
- closed = true;
1683
- // These streams never end on their own, so they are closed explicitly;
1684
- // otherwise server.close() would wait for a window that stays open.
1685
- updates.closeAll();
1686
- for (const key of [...webSessions.keys()])
1687
- closeWebSession(key);
1688
- if (!server.listening) {
1689
- resolve();
1690
- return;
1691
- }
1692
- server.close(() => resolve());
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
- await listenRetry(host);
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.
@@ -0,0 +1,7 @@
1
+ /** The detached process owns browser conversations for their entire lifetime. */
2
+ export declare function runWebService(options: {
3
+ port: number;
4
+ host?: string;
5
+ auth: boolean;
6
+ tray: boolean;
7
+ }): Promise<void>;
@@ -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
+ }