newmark-agent 0.6.4 → 0.6.8

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 (76) hide show
  1. package/config.example.json +13 -3
  2. package/dist/cli-commands.d.ts +1 -1
  3. package/dist/cli-commands.js +54 -3
  4. package/dist/cli-discovery.js +3 -0
  5. package/dist/conversation-utility-host.bundle.cjs +4472 -1767
  6. package/dist/conversation-utility-host.js +94 -7
  7. package/dist/core/agent.d.ts +126 -12
  8. package/dist/core/agent.js +785 -172
  9. package/dist/core/agentKernelRunner.d.ts +7 -1
  10. package/dist/core/agentKernelRunner.js +160 -20
  11. package/dist/core/autoRouter.d.ts +49 -44
  12. package/dist/core/autoRouter.js +117 -302
  13. package/dist/core/computerUseSession.js +1 -1
  14. package/dist/core/config.js +27 -11
  15. package/dist/core/continuation/contracts.d.ts +1 -1
  16. package/dist/core/continuation/store.d.ts +91 -1
  17. package/dist/core/continuation/store.js +291 -61
  18. package/dist/core/conversationKernel.d.ts +49 -7
  19. package/dist/core/conversationKernel.js +303 -51
  20. package/dist/core/conversationStateDocument.d.ts +18 -0
  21. package/dist/core/conversationStateDocument.js +88 -0
  22. package/dist/core/conversationVisualBudget.d.ts +74 -0
  23. package/dist/core/conversationVisualBudget.js +153 -0
  24. package/dist/core/electronUtilityAgentClient.d.ts +3 -2
  25. package/dist/core/electronUtilityAgentClient.js +32 -8
  26. package/dist/core/electronUtilityRuntimePool.d.ts +28 -3
  27. package/dist/core/electronUtilityRuntimePool.js +93 -24
  28. package/dist/core/fileWriteObservation.d.ts +41 -0
  29. package/dist/core/fileWriteObservation.js +454 -0
  30. package/dist/core/hostRuntimeHooks.d.ts +23 -0
  31. package/dist/core/hostRuntimeHooks.js +17 -0
  32. package/dist/core/jevDecision.d.ts +144 -0
  33. package/dist/core/jevDecision.js +226 -0
  34. package/dist/core/memoryProbe.d.ts +24 -0
  35. package/dist/core/memoryProbe.js +104 -0
  36. package/dist/core/mobilePairing.d.ts +7 -1
  37. package/dist/core/mobilePairing.js +9 -1
  38. package/dist/core/performanceDiagnostics.d.ts +1 -1
  39. package/dist/core/routeDecisionValidator.d.ts +86 -0
  40. package/dist/core/routeDecisionValidator.js +249 -0
  41. package/dist/core/routeEligibility.d.ts +52 -0
  42. package/dist/core/routeEligibility.js +108 -0
  43. package/dist/core/runtimeMemoryBudget.d.ts +6 -0
  44. package/dist/core/runtimeMemoryBudget.js +12 -0
  45. package/dist/core/toolPolicy.d.ts +1 -1
  46. package/dist/core/toolPolicy.js +1 -1
  47. package/dist/core/utilityAgentProtocol.d.ts +8 -10
  48. package/dist/core/utilityHostToolRouter.js +2 -0
  49. package/dist/core/visualDownscale.d.ts +6 -0
  50. package/dist/core/visualDownscale.js +137 -0
  51. package/dist/core/wslAgentClient.d.ts +1 -2
  52. package/dist/core/wslAgentClient.js +0 -8
  53. package/dist/core/wslAgentProtocol.d.ts +1 -10
  54. package/dist/core/wslAgentRuntimePool.d.ts +1 -3
  55. package/dist/core/wslAgentRuntimePool.js +17 -22
  56. package/dist/llm/provider.d.ts +3 -1
  57. package/dist/llm/provider.js +53 -9
  58. package/dist/main.js +107 -30
  59. package/dist/preload.js +3 -1
  60. package/dist/server.js +30 -18
  61. package/dist/tools/computerUse.d.ts +6 -0
  62. package/dist/tools/computerUse.js +406 -21
  63. package/dist/tools/computerUsePowerShellHost.d.ts +6 -1
  64. package/dist/tools/computerUsePowerShellHost.js +105 -31
  65. package/dist/tools/index.d.ts +3 -4
  66. package/dist/tools/index.js +146 -66
  67. package/dist/tui/src/adapters/core-runtime-adapter.js +13 -1
  68. package/dist/tui/src/i18n.js +12 -1
  69. package/dist/tui/src/render.js +9 -2
  70. package/dist/tui/src/settings-schema.js +19 -0
  71. package/dist/tui/src/state.js +69 -1
  72. package/dist/ui/index.html +651 -233
  73. package/dist/ui/lucide-sprite.svg +0 -8
  74. package/dist/wsl-agent-host.bundle.cjs +4302 -1767
  75. package/dist/wsl-agent-host.js +0 -3
  76. package/package.json +39 -10
@@ -1,6 +1,7 @@
1
1
  import { Agent } from './agent';
2
2
  import { StreamToken } from './types';
3
3
  import { type ToolchainCore } from '../toolchain';
4
+ import { type ConversationVisualBudget } from './conversationVisualBudget';
4
5
  export declare function filterPublicAssistantDelta(agent: Agent, delta: string): string;
5
6
  export declare function resetPublicAssistantDeltaFilter(agent: Agent): void;
6
7
  interface KernelTextContent {
@@ -143,7 +144,12 @@ declare function visualFallbackImageInput(agent: Agent, name: string, text: stri
143
144
  export declare function toolResultObjectiveOutcome(result: string): boolean | undefined;
144
145
  declare function toKernelMessagesFromHistory(history: Array<Record<string, unknown>>, agent: Agent): KernelMessage[];
145
146
  declare function toProviderToolDefinitions(tools: KernelTool[]): unknown[];
146
- declare function fromKernelMessages(messages: KernelMessage[], includeEphemeralImages?: boolean): Array<Record<string, unknown>>;
147
+ declare function fromKernelMessages(messages: KernelMessage[], includeEphemeralImages?: boolean, visualBudget?: ConversationVisualBudget, onVisualRelease?: (info: {
148
+ releasedImages: number;
149
+ releasedBytes: number;
150
+ retainedBytes: number;
151
+ budget: ConversationVisualBudget;
152
+ }) => void): Array<Record<string, unknown>>;
147
153
  declare function imagePathToOpenAIContentPart(imagePath: string): Record<string, unknown> | null;
148
154
  export declare const agentKernelRunnerInternals: {
149
155
  buildRequestTaskFocus: typeof buildRequestTaskFocus;
@@ -48,8 +48,13 @@ const toolPolicy_1 = require("./toolPolicy");
48
48
  const performanceDiagnostics_1 = require("./performanceDiagnostics");
49
49
  const agentKernelDiagnostics_1 = require("./agentKernelDiagnostics");
50
50
  const toolchain_1 = require("../toolchain");
51
+ const conversationVisualBudget_1 = require("./conversationVisualBudget");
52
+ const visualDownscale_1 = require("./visualDownscale");
51
53
  const emptyResponseRetry_1 = require("./emptyResponseRetry");
52
54
  const publicStreamFilters = new WeakMap();
55
+ /** dev-0.6.6: throttles the "screenshots released" work-status line. */
56
+ let visualReleaseReportedAt = 0;
57
+ const VISUAL_MIB = 1024 ** 2;
53
58
  const brokerOnlyAssistantBuffers = new WeakMap();
54
59
  const BROKER_PREFACE_BUFFER_CHARS = 96;
55
60
  const HIDDEN_LINE_PREFIXES = [
@@ -460,7 +465,11 @@ async function runAgentKernel(agent) {
460
465
  agent.chatMessages.push({ role: 'user', content: KernelMessageText(msg), mode: agent.modeName(), model: agent.model, timestamp: agent.nowLabel() });
461
466
  agent.history.push(toHistoryMessage(msg));
462
467
  }
463
- agent.saveWorkspaceConversationState();
468
+ // Mid-turn persistence is coalesced (see Agent.scheduleStoredConversationState):
469
+ // the terminal save at the end of the run still writes synchronously, so
470
+ // durability at turn boundaries is unchanged while per-turn full-document
471
+ // rewrites drop from one-per-event to at most one per throttle window.
472
+ agent.saveWorkspaceConversationState(false);
464
473
  }
465
474
  await kernel.prompt(promptMessages);
466
475
  const assistant = lastAssistant;
@@ -519,7 +528,7 @@ async function runAgentKernel(agent) {
519
528
  agent.emitWorkEvent({ type: 'final_response', content: visualFallback });
520
529
  agent.chatMessages.push({ role: 'assistant', content: visualFallback, mode: agent.modeName(), model: agent.model, timestamp: agent.nowLabel(), runId: agent.currentWorkRunId() || undefined });
521
530
  agent.history.push({ role: 'assistant', content: visualFallback, run_id: agent.currentWorkRunId() || undefined });
522
- agent.saveWorkspaceConversationState();
531
+ agent.saveWorkspaceConversationState(false);
523
532
  agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
524
533
  lastTurn = { ...lastTurn, text: visualFallback, errorMessage: '', stopReason: 'stop' };
525
534
  }
@@ -658,7 +667,9 @@ async function runAgentKernel(agent) {
658
667
  agent.attachAgentKernelRuntime(null);
659
668
  }
660
669
  agent.status = 'idle';
661
- agent.saveWorkspaceConversationState();
670
+ // Coalesced: the caller (Agent.process) flushes synchronously right after,
671
+ // and every in-memory reader already sees this status.
672
+ agent.saveWorkspaceConversationState(false);
662
673
  return agent.sanitizeVisibleTokens(tokens);
663
674
  function streamWithNewmarkProvider(currentAgent, compat) {
664
675
  return async (model, context, options) => {
@@ -696,7 +707,15 @@ async function runAgentKernel(agent) {
696
707
  // endpoint owns its real limit; protocols that require a positive
697
708
  // max_tokens resolve one internally.
698
709
  const maxTokens = 0;
699
- const newmarkMessages = fromKernelMessages(context.messages).map(message => message.role === 'assistant'
710
+ const requestVisualBudget = visualBudgetForAgent(currentAgent);
711
+ const newmarkMessages = fromKernelMessages(context.messages, true, requestVisualBudget, info => {
712
+ // Observable, throttled: one status line per run so the user learns
713
+ // that screenshots were reclaimed instead of silently losing them.
714
+ if (visualReleaseReportedAt && Date.now() - visualReleaseReportedAt < 60_000)
715
+ return;
716
+ visualReleaseReportedAt = Date.now();
717
+ currentAgent.recordWorkStatus(`Released ${info.releasedImages} older ComputerUse/BrowserControl screenshot(s) (~${Math.round(info.releasedBytes / VISUAL_MIB)} MiB) to stay within the ${(0, conversationVisualBudget_1.describeVisualBudget)(info.budget)} budget.`);
718
+ }).map(message => message.role === 'assistant'
700
719
  // Use the same public-content boundary on its first provider
701
720
  // submission and after persistence. Trimming only the durable
702
721
  // copy changes earlier tool-call envelopes on mailbox recovery.
@@ -1051,7 +1070,7 @@ async function handleKernelEvent(agent, event, tokens) {
1051
1070
  // Otherwise a cold continuation loses an already-read instruction
1052
1071
  // and deletes that message from the provider's retained prefix.
1053
1072
  agent.history.push({ ...toHistoryMessage(event.message), run_id: agent.currentWorkRunId() || undefined });
1054
- agent.saveWorkspaceConversationState(true);
1073
+ agent.saveWorkspaceConversationState(false);
1055
1074
  }
1056
1075
  agent.notifyAgentKernelUserMessageStart(text, event.message.clientMessageId);
1057
1076
  }
@@ -1158,7 +1177,7 @@ async function handleKernelEvent(agent, event, tokens) {
1158
1177
  historyMessage.content = text;
1159
1178
  historyMessage.run_id = agent.currentWorkRunId() || undefined;
1160
1179
  agent.history.push(historyMessage);
1161
- agent.saveWorkspaceConversationState();
1180
+ agent.saveWorkspaceConversationState(false);
1162
1181
  }
1163
1182
  resetPublicAssistantDeltaFilter(agent);
1164
1183
  resetAssistantToolVisibility(agent);
@@ -1169,6 +1188,9 @@ async function handleKernelEvent(agent, event, tokens) {
1169
1188
  ...(!event.message.isError && receipt ? { subagent_settlement_receipt: { ...receipt } } : {}),
1170
1189
  };
1171
1190
  agent.history.push(historyMessage);
1191
+ // Kept synchronous on purpose: the catch below clears the settlement
1192
+ // receipt so a later notification cannot mistake an unsaved result for a
1193
+ // committed one, which requires the write failure to surface here.
1172
1194
  try {
1173
1195
  agent.saveWorkspaceConversationState();
1174
1196
  }
@@ -1790,8 +1812,17 @@ function visualFallbackImageInput(agent, name, text) {
1790
1812
  if (name !== 'pdf_read' && nested.action !== 'observe' && nested.action !== 'app_observe')
1791
1813
  return {};
1792
1814
  const directImage = String(nested.vision_image_data_url || '');
1793
- if (/^data:image\/(?:png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$/i.test(directImage) && directImage.length <= 2 * 1024 * 1024) {
1794
- return { image: directImage, mimeType: directImage.slice(5, directImage.indexOf(';')).toLowerCase() };
1815
+ if (/^data:image\/(?:png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$/i.test(directImage)) {
1816
+ // dev-0.6.6: ComputerUse/BrowserControl screenshots are the dominant
1817
+ // memory consumer of a long conversation, so the accepted single-image
1818
+ // size follows the machine's available memory instead of a fixed 2 MiB.
1819
+ const budget = visualBudgetForAgent(agent);
1820
+ const fitted = (0, conversationVisualBudget_1.visualDataUrlBytes)(directImage) <= budget.maxSingleImageBytes
1821
+ ? directImage
1822
+ : (0, visualDownscale_1.downscaleDataUrlToBytes)(directImage, budget.maxSingleImageBytes);
1823
+ if (fitted)
1824
+ return { image: fitted, mimeType: fitted.slice(5, fitted.indexOf(';')).toLowerCase() };
1825
+ return {};
1795
1826
  }
1796
1827
  const screenshotPath = String(nested.vision_image_path || '');
1797
1828
  if (!screenshotPath)
@@ -1803,6 +1834,47 @@ function visualFallbackImageInput(agent, name, text) {
1803
1834
  return {};
1804
1835
  }
1805
1836
  }
1837
+ const visualBudgetCache = new WeakMap();
1838
+ /**
1839
+ * The conversation's visual budget for right now. Sampled at most once per
1840
+ * second per agent so a tool-heavy round does not re-read OS memory state on
1841
+ * every screenshot.
1842
+ */
1843
+ function visualBudgetForAgent(agent) {
1844
+ const cached = visualBudgetCache.get(agent);
1845
+ const now = Date.now();
1846
+ if (cached && now - cached.at < 1000)
1847
+ return cached.budget;
1848
+ const usage = (() => {
1849
+ try {
1850
+ return process.memoryUsage();
1851
+ }
1852
+ catch {
1853
+ return { rss: 0 };
1854
+ }
1855
+ })();
1856
+ const budget = (0, conversationVisualBudget_1.conversationVisualBudget)({ rssBytes: Number(usage?.rss || 0) });
1857
+ visualBudgetCache.set(agent, { at: now, budget });
1858
+ return budget;
1859
+ }
1860
+ /** Bytes an image part costs when materialized into the provider request. */
1861
+ function imagePartBytes(part) {
1862
+ const inline = String(part.image || '');
1863
+ if (inline.startsWith('data:'))
1864
+ return (0, conversationVisualBudget_1.visualDataUrlBytes)(inline);
1865
+ const imagePath = String(part.imagePath || '');
1866
+ if (!imagePath)
1867
+ return 0;
1868
+ try {
1869
+ // Materializing a path-based screenshot produces `data:<mime>;base64,<...>`;
1870
+ // budget the base64 payload length the request will actually hold.
1871
+ const size = fs.statSync(imagePath).size;
1872
+ return 4 * Math.ceil(size / 3);
1873
+ }
1874
+ catch {
1875
+ return 0;
1876
+ }
1877
+ }
1806
1878
  async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSettlementReceipt) {
1807
1879
  const stopToolTimer = (0, performanceDiagnostics_1.performanceTimer)('tool_execution', { conversationId: agent.activeConversationId, detail: { tool: name } });
1808
1880
  try {
@@ -1892,6 +1964,8 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
1892
1964
  conversationId: agent.activeConversationId || 'default',
1893
1965
  actorId: agent.runtimeActorId,
1894
1966
  workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
1967
+ runtimeKey: process.env.NEWMARK_RUNTIME_KEY || undefined,
1968
+ runId: agent.currentWorkRunId() || undefined,
1895
1969
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
1896
1970
  signal,
1897
1971
  });
@@ -1944,6 +2018,8 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
1944
2018
  conversationId: agent.activeConversationId || 'default',
1945
2019
  actorId: agent.runtimeActorId,
1946
2020
  workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
2021
+ runtimeKey: process.env.NEWMARK_RUNTIME_KEY || undefined,
2022
+ runId: agent.currentWorkRunId() || undefined,
1947
2023
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
1948
2024
  inspectBrowserImage: name === 'browser_use' ? (dataUrl, prompt) => agent.inspectBrowserImage(dataUrl, prompt, signal) : undefined,
1949
2025
  allowEphemeralVisionImage: (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read'),
@@ -1953,8 +2029,11 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
1953
2029
  throw abortError();
1954
2030
  trackFileDiff(agent, name, args);
1955
2031
  const objectiveResult = toolResultObjectiveOutcome(result);
2032
+ // Objective postconditions are operational health evidence only. Auto Router
2033
+ // v2 keeps tool outcomes out of model preference: the observation can demote
2034
+ // an endpoint's live health, and it can never teach a future route.
1956
2035
  if (objectiveResult !== undefined)
1957
- agent.recordObjectiveRouteResult(objectiveResult);
2036
+ agent.recordRouteToolOutcome(objectiveResult);
1958
2037
  return result;
1959
2038
  }
1960
2039
  finally {
@@ -2081,9 +2160,53 @@ function toProviderToolDefinitions(tools) {
2081
2160
  },
2082
2161
  }));
2083
2162
  }
2084
- function fromKernelMessages(messages, includeEphemeralImages = true) {
2085
- return messages.flatMap(message => {
2086
- const projected = toHistoryMessage(message, includeEphemeralImages);
2163
+ function fromKernelMessages(messages, includeEphemeralImages = true, visualBudget, onVisualRelease) {
2164
+ const budget = visualBudget || (0, conversationVisualBudget_1.conversationVisualBudget)();
2165
+ // dev-0.6.6: a late-stage ComputerUse/BrowserControl conversation stores one
2166
+ // screenshot per observation. Materializing all of them per request is what
2167
+ // pushed the utility host to a 3 GB V8 abort, so the newest screenshots are
2168
+ // kept within this machine's visual budget and older ones are released
2169
+ // (text/tool results stay untouched).
2170
+ const candidates = [];
2171
+ messages.forEach((message, index) => {
2172
+ if (message.role !== 'toolResult')
2173
+ return;
2174
+ const image = message.content.find((part) => part.type === 'image');
2175
+ if (!image)
2176
+ return;
2177
+ candidates.push({ key: String(index), bytes: imagePartBytes(image) });
2178
+ });
2179
+ const plan = (0, conversationVisualBudget_1.planVisualRetention)(candidates, budget);
2180
+ if (plan.released.length && onVisualRelease) {
2181
+ onVisualRelease({
2182
+ releasedImages: plan.released.length,
2183
+ releasedBytes: plan.releasedBytes,
2184
+ retainedBytes: plan.retainedBytes,
2185
+ budget,
2186
+ });
2187
+ }
2188
+ return messages.flatMap((message, index) => {
2189
+ const key = String(index);
2190
+ const hadImage = message.role === 'toolResult'
2191
+ && message.content.some(part => part.type === 'image');
2192
+ const imageAllowed = !hadImage || plan.keep.has(key);
2193
+ const projected = toHistoryMessage(message, includeEphemeralImages && imageAllowed, budget);
2194
+ if (hadImage && !imageAllowed) {
2195
+ // Tell the model the screenshot existed but was released, so it can
2196
+ // re-observe instead of assuming the picture was empty.
2197
+ // Kept to one short line: a long conversation can release hundreds of
2198
+ // historic screenshots, and the note must not become its own payload.
2199
+ const releaseNote = `[visual_memory_budget] screenshot released (${budget.pressure} memory budget); re-run observe if you need it again.`;
2200
+ if (typeof projected.content === 'string')
2201
+ projected.content = `${projected.content}\n${releaseNote}`;
2202
+ else if (Array.isArray(projected.content)) {
2203
+ const textPart = projected.content.find(part => part?.type === 'text');
2204
+ if (textPart)
2205
+ textPart.text = `${String(textPart.text || '')}\n${releaseNote}`;
2206
+ else
2207
+ projected.content.unshift({ type: 'text', text: releaseNote });
2208
+ }
2209
+ }
2087
2210
  if (message.role !== 'toolResult' || !Array.isArray(projected.content))
2088
2211
  return [projected];
2089
2212
  const parts = projected.content;
@@ -2122,7 +2245,7 @@ function publicHistoryFromKernelMessages(messages) {
2122
2245
  return [toHistoryMessage({ ...message, content: publicContent }, false)];
2123
2246
  });
2124
2247
  }
2125
- function toHistoryMessage(message, includeEphemeralImages = false) {
2248
+ function toHistoryMessage(message, includeEphemeralImages = false, visualBudget) {
2126
2249
  if (message.role === 'user') {
2127
2250
  if (typeof message.content === 'string')
2128
2251
  return {
@@ -2154,13 +2277,7 @@ function toHistoryMessage(message, includeEphemeralImages = false) {
2154
2277
  const ephemeralImage = message.content.find((c) => c.type === 'image');
2155
2278
  const imagePath = ephemeralImage?.imagePath || '';
2156
2279
  const directImage = ephemeralImage?.image || '';
2157
- const imagePart = includeEphemeralImages
2158
- ? (imagePath
2159
- ? imagePathToOpenAIContentPart(imagePath)
2160
- : directImage.startsWith('data:image/')
2161
- ? { type: 'image_url', image_url: { url: directImage } }
2162
- : null)
2163
- : null;
2280
+ const imagePart = includeEphemeralImages ? materializeVisualImagePart(imagePath, directImage, visualBudget) : null;
2164
2281
  if (imagePart && ephemeralImage) {
2165
2282
  // Tool-result images are one provider-input capability, not history. Once
2166
2283
  // this request has materialized the image part, consume it from the live
@@ -2186,6 +2303,29 @@ function imagePathToOpenAIContentPart(imagePath) {
2186
2303
  return null;
2187
2304
  }
2188
2305
  }
2306
+ /**
2307
+ * dev-0.6.6: materialize one screenshot into a provider image part while
2308
+ * respecting the conversation's per-image cap. Oversized screenshots are
2309
+ * downscaled (PNG, then JPEG) instead of being dropped, so a memory-tight
2310
+ * machine keeps vision and the agent keeps working.
2311
+ */
2312
+ function materializeVisualImagePart(imagePath, directImage, visualBudget) {
2313
+ const budget = visualBudget || (0, conversationVisualBudget_1.conversationVisualBudget)();
2314
+ let dataUrl = '';
2315
+ if (String(directImage || '').startsWith('data:image/'))
2316
+ dataUrl = String(directImage);
2317
+ else if (imagePath)
2318
+ dataUrl = imagePathToDataUrl(imagePath) || '';
2319
+ if (!dataUrl)
2320
+ return null;
2321
+ if ((0, conversationVisualBudget_1.visualDataUrlBytes)(dataUrl) > budget.maxSingleImageBytes) {
2322
+ const fitted = (0, visualDownscale_1.downscaleDataUrlToBytes)(dataUrl, budget.maxSingleImageBytes);
2323
+ if (!fitted)
2324
+ return null;
2325
+ dataUrl = fitted;
2326
+ }
2327
+ return { type: 'image_url', image_url: { url: dataUrl } };
2328
+ }
2189
2329
  exports.agentKernelRunnerInternals = {
2190
2330
  buildRequestTaskFocus,
2191
2331
  buildBuildContextBootstrap,
@@ -18,24 +18,29 @@ export type ModelSelection = {
18
18
  policyId: string;
19
19
  subset?: DeploymentRef[];
20
20
  };
21
- export type RouteMode = 'quality' | 'balanced' | 'cost' | 'speed';
21
+ export type RouteMode = 'conservative' | 'balanced' | 'aggressive';
22
22
  export type RoutePrivacy = 'default' | 'no_training' | 'zdr';
23
23
  export type TaskClass = 'chat' | 'coding' | 'reasoning' | 'long_context' | 'vision' | 'image_generation' | 'tool_use' | 'computer_use';
24
24
  export type ValidationLevel = 'discovered' | 'legacy_basic' | 'basic' | 'standard' | 'extended';
25
25
  export type ValidationStatus = 'verified' | 'degraded' | 'unavailable' | 'auth_error' | 'rate_limited' | 'invalid_config';
26
26
  export interface RoutePolicy {
27
27
  mode: RouteMode;
28
- maxQualityLoss: number;
29
- maxExpectedCostUsd?: number;
30
28
  allowPreview: boolean;
31
29
  privacy: RoutePrivacy;
32
30
  requiredCapabilities: string[];
33
31
  dataRegion?: string;
34
32
  requiredProtocolParameters?: string[];
33
+ maxExpectedCostUsd?: number;
35
34
  }
36
35
  export interface AutoRouteCandidate {
37
36
  deployment: DeploymentRef;
38
37
  enabled: boolean;
38
+ /**
39
+ * Set by the Agent when a deployment is explicitly unusable right now
40
+ * (for example a provider-reported balance exhaustion window). It carries a
41
+ * guard reason instead of silently mutating `enabled`.
42
+ */
43
+ unavailableReason?: string;
39
44
  validation: {
40
45
  level: ValidationLevel;
41
46
  status: ValidationStatus;
@@ -49,39 +54,24 @@ export interface AutoRouteCandidate {
49
54
  supportedProtocolParameters?: string[];
50
55
  expectedInputCostUsdPerM?: number;
51
56
  expectedOutputCostUsdPerM?: number;
57
+ /** Current operational measurements. Never persisted as a preference. */
52
58
  latencyMs?: number;
53
59
  reliability?: number;
54
60
  toolValidity?: number;
55
61
  throughput?: number;
56
- qualityByTask?: Partial<Record<TaskClass, {
57
- successes: number;
58
- attempts: number;
59
- }>>;
62
+ circuit?: 'closed' | 'open' | 'half_open';
63
+ /** Explicit user configuration (`route_preference`), not learned history. */
60
64
  preference?: number;
61
65
  fallbackOnly?: boolean;
62
66
  }
63
67
  export interface RouteRequest {
64
68
  transactionId: string;
65
- affinityKey: string;
66
69
  taskText: string;
67
70
  estimatedInputTokens: number;
68
71
  expectedOutputTokens: number;
69
72
  requiredCapabilities: string[];
70
73
  batch?: boolean;
71
74
  }
72
- export interface RankedRouteCandidate {
73
- deployment: DeploymentRef;
74
- quality: number;
75
- utility: number;
76
- expectedCostUsd?: number;
77
- components: {
78
- cost: number;
79
- reliability: number;
80
- speed: number;
81
- cache: number;
82
- preference: number;
83
- };
84
- }
85
75
  export type RouteAttemptStatus = 'planned' | 'success' | 'failed' | 'blocked';
86
76
  export interface RouteAttempt {
87
77
  deployment: DeploymentRef;
@@ -97,15 +87,24 @@ export interface RouteDecision {
97
87
  routeId: string;
98
88
  requestedSelection: ModelSelection;
99
89
  policyVersion: string;
90
+ decisionSource: 'jev';
100
91
  catalogSnapshotHash: string;
101
92
  taskClasses: TaskClass[];
102
93
  excludedCandidates: Array<{
103
94
  deployment: DeploymentRef;
104
95
  reasons: string[];
105
96
  }>;
106
- rankedCandidates: RankedRouteCandidate[];
107
97
  resolvedDeployment?: DeploymentRef;
108
- pinReason?: 'transaction' | 'cache_affinity';
98
+ /** Ordered recovery hints produced by the decision engine, already validated. */
99
+ alternatives: DeploymentRef[];
100
+ jev: {
101
+ modelVersion: string;
102
+ confidence?: number;
103
+ reasonCodes: string[];
104
+ latencyMs: number;
105
+ };
106
+ decisionError?: string;
107
+ pinReason?: 'transaction';
109
108
  attempts: RouteAttempt[];
110
109
  finalStatus: 'resolved' | 'no_candidate' | 'fixed_unavailable' | 'retrying' | 'succeeded' | 'failed' | 'blocked';
111
110
  retryBudgetMs?: number;
@@ -121,34 +120,45 @@ export interface RouteFailure {
121
120
  export interface PlannedRouteAttempt extends RouteAttempt {
122
121
  status: 'planned';
123
122
  }
124
- export interface RouteFeedbackEvent {
125
- deployment: DeploymentRef;
126
- taskClass: TaskClass;
127
- score: number;
128
- source: 'manual_switch' | 'explicit_rating' | 'objective_success';
129
- at?: number;
130
- }
123
+ export declare const ROUTE_POLICY_VERSION = "newmark-auto-v2";
131
124
  export declare function normalizeAutoPreference(value: string): RouteMode;
132
125
  export declare function defaultRoutePolicy(mode?: RouteMode): RoutePolicy;
133
126
  export declare function classifyTaskClasses(taskText: string, requiredCapabilities: string[], estimatedInputTokens?: number): TaskClass[];
134
127
  export declare function classifyRouteFailure(error: unknown): RouteFailure;
135
- export declare class AutoRouter {
128
+ export declare function createRouteId(): string;
129
+ export declare function catalogSnapshotHash(candidates: AutoRouteCandidate[]): string;
130
+ /**
131
+ * Operational execution controller.
132
+ *
133
+ * It owns endpoint health, the circuit breaker, build-scoped transaction pins
134
+ * and the provider-local recovery ladder. It never ranks models and never
135
+ * learns a preference; health observations only decide whether an endpoint is
136
+ * attempted right now.
137
+ */
138
+ export declare class RouteExecutionController {
136
139
  private readonly now;
137
140
  private readonly policyVersion;
138
- private readonly affinityTtlMs;
139
- private readonly switchThreshold;
140
141
  private readonly transactionPins;
141
- private readonly affinities;
142
142
  private readonly endpointHealth;
143
- private readonly feedback;
144
143
  constructor(options?: {
145
144
  now?: () => number;
146
145
  policyVersion?: string;
147
- affinityTtlMs?: number;
148
- switchThreshold?: number;
149
146
  });
150
- route(selection: ModelSelection, policy: RoutePolicy, candidates: AutoRouteCandidate[], request: RouteRequest): RouteDecision;
147
+ version(): string;
148
+ /** Execution-consistency pin for one Build / route transaction. */
149
+ pinTransaction(transactionId: string, deployment: DeploymentRef): void;
150
+ transactionPin(transactionId: string): DeploymentRef | undefined;
151
151
  endTransaction(transactionId: string): void;
152
+ /**
153
+ * Provider-local recovery ladder.
154
+ *
155
+ * Order of preference: retry the same deployment (when the failure is
156
+ * retryable inside the retry budget), then an equivalent deployment of the
157
+ * same logical model group, then the alternates the decision engine already
158
+ * validated (in the order it produced them), then explicit fallback-only
159
+ * models. Recovery never crosses the failed provider boundary and never
160
+ * re-runs model selection.
161
+ */
152
162
  planAttempts(decision: RouteDecision, candidates: AutoRouteCandidate[], failure: {
153
163
  error: RouteFailure;
154
164
  streamCommitted: boolean;
@@ -170,14 +180,9 @@ export declare class AutoRouter {
170
180
  toolValidity: number;
171
181
  circuit: 'closed' | 'open' | 'half_open';
172
182
  };
173
- recordFeedback(event: RouteFeedbackEvent): boolean;
174
- clearLearnedPreferences(): void;
175
- learnedPreference(deployment: DeploymentRef, taskClasses: TaskClass[]): number;
176
- private rankEligible;
177
- private rankCandidate;
178
- private affinityKey;
179
183
  private healthFor;
180
184
  private passedInitialHardFilters;
181
185
  private circuitState;
182
186
  }
187
+ export declare function validationEvidenceIsStale(checkedAt: string | undefined, now?: number): boolean;
183
188
  //# sourceMappingURL=autoRouter.d.ts.map