newmark-agent 0.4.7 → 0.4.9

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.
@@ -327560,6 +327560,7 @@ var NATIVE_TOOL_CATALOG = [
327560
327560
  { name: "browser_reload", label: "Browser reload", description: "Reload the built-in browser.", category: "browser", defaultEnabled: true },
327561
327561
  { name: "browser_cdp", label: "Browser CDP", description: "Run an advanced Chrome DevTools Protocol command.", category: "browser", defaultEnabled: true },
327562
327562
  { name: "browser_use", label: "Browser Use", description: "Observe and control the built-in browser through scoped opaque element refs and structured receipts.", category: "browser", defaultEnabled: true },
327563
+ { name: "screen_capture", label: "Active screenshot", description: "Capture the Windows desktop or one visible application without starting the full Computer Use control surface.", category: "desktop", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327563
327564
  { name: "computer_use", label: "Computer Use", description: "Observe and control Windows desktop UI with screenshots and semantic objects.", category: "desktop", defaultEnabled: true },
327564
327565
  { name: "image_inspect", label: "Image inspect", description: "Crop and magnify images submitted in the current conversation for closer visual inspection.", category: "core", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327565
327566
  { name: "image_display", label: "Image display", description: "Present a workspace PNG/JPEG inside the current Build Block.", category: "core", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -335754,6 +335755,7 @@ var import_crypto6 = require("crypto");
335754
335755
  // src/core/toolPolicy.ts
335755
335756
  var REQUIRED_TOOLS = /* @__PURE__ */ new Set(["pwd", "read", "glob", "grep"]);
335756
335757
  var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335758
+ "screen_capture",
335757
335759
  "image_inspect",
335758
335760
  "image_display",
335759
335761
  "ocr_read",
@@ -335786,6 +335788,7 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335786
335788
  "branch_create"
335787
335789
  ]);
335788
335790
  var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335791
+ "screen_capture",
335789
335792
  "task_read",
335790
335793
  "pwd",
335791
335794
  "read",
@@ -335833,6 +335836,7 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335833
335836
  "git_status",
335834
335837
  "file_audit",
335835
335838
  "repo_security_audit",
335839
+ "screen_capture",
335836
335840
  "SubAgent"
335837
335841
  ]);
335838
335842
  function isConcurrencySafeTool(name50, riskLevel) {
@@ -336912,6 +336916,13 @@ var ToolExecutor = class {
336912
336916
  max_refs: { type: "number" },
336913
336917
  attribute: { type: "string" }
336914
336918
  }, ["action"]),
336919
+ t3("screen_capture", "Read-only active Windows screenshot available without provisioning or starting full Computer Use. Capture the whole desktop or one visible application. The image is a one-use model input and is deleted immediately; semantic Windows UI objects are returned alongside it.", {
336920
+ target: { type: "string", enum: ["desktop", "application"], description: "Capture the whole desktop or one visible application. Defaults to desktop." },
336921
+ app_target: { type: "string", description: "For target=application: application title, process name, process id, or window handle." },
336922
+ capture_max_width: { type: "number", minimum: 320, maximum: 2048, description: "Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged." },
336923
+ capture_max_height: { type: "number", minimum: 240, maximum: 2048, description: "Maximum ephemeral screenshot height; defaults to 960. Aspect ratio is preserved and the source is never enlarged." },
336924
+ max_chars: { type: "number", minimum: 1e3, maximum: 1e5, description: "Maximum returned semantic UI text characters." }
336925
+ }, []),
336915
336926
  t3("computer_use", "Native Windows desktop control with persistent observation/action helpers. Use observe/app_observe before acting. sequence may perform up to three stable low-risk move/click/scroll/wait/app_activate steps and stops when focus, window, menu, dialog, scene, or risk changes. Vision screenshots remain one-time inputs and are deleted immediately.", {
336916
336927
  action: { type: "string", enum: ["observe", "app_list", "app_observe", "sequence", "takeover_start", "takeover_stop", "move", "click", "scroll", "type", "key", "wait", "app_activate", "app_click", "app_scroll", "app_type", "app_key"] },
336917
336928
  capture_max_width: { type: "number", minimum: 320, maximum: 2048, description: "For observe/app_observe only. Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged." },
@@ -337402,6 +337413,44 @@ var ToolExecutor = class {
337402
337413
  result: parsed
337403
337414
  }, null, 2);
337404
337415
  }
337416
+ case "screen_capture": {
337417
+ const target = g2("target").toLowerCase() === "application" ? "application" : "desktop";
337418
+ if (process.env.NEWMARK_WSL_DISTRO) {
337419
+ const result = await requestWindowsHostTool("screen_capture", args, {
337420
+ conversationId: context.conversationId || "default",
337421
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337422
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337423
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
337424
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337425
+ mode: context.mode || "build"
337426
+ }, 12e4, context.signal);
337427
+ return typeof result === "string" ? result : JSON.stringify(result);
337428
+ }
337429
+ if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337430
+ const result = await requestUtilityHostTool("screen_capture", args, {
337431
+ conversationId: context.conversationId || "default",
337432
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337433
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337434
+ workspacePath: context.workspacePath || wsPath,
337435
+ backend: "utility",
337436
+ mode: context.mode || "build",
337437
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true
337438
+ }, 12e4, context.signal);
337439
+ return typeof result === "string" ? result : JSON.stringify(result);
337440
+ }
337441
+ return await runComputerUse({
337442
+ action: target === "application" ? "app_observe" : "observe",
337443
+ appTarget: g2("app_target"),
337444
+ windowHandle: target === "application" ? g2("app_target") : "",
337445
+ maxChars: Number(args.max_chars || 3e4),
337446
+ workspacePath: wsPath,
337447
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337448
+ captureMaxWidth: Number(args.capture_max_width),
337449
+ captureMaxHeight: Number(args.capture_max_height),
337450
+ invocation: context.invocation,
337451
+ ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`
337452
+ });
337453
+ }
337405
337454
  case "computer_use": {
337406
337455
  const action = normalizeComputerUseAction(g2("action"));
337407
337456
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
@@ -337608,7 +337657,7 @@ var ToolExecutor = class {
337608
337657
  }
337609
337658
  hostSupportsTool(name50) {
337610
337659
  if (name50.startsWith("browser_") && !this.hostProfile.electronBrowser) return false;
337611
- if (name50 === "computer_use" && !this.hostProfile.windowsComputerUse) return false;
337660
+ if ((name50 === "computer_use" || name50 === "screen_capture") && !this.hostProfile.windowsComputerUse) return false;
337612
337661
  return true;
337613
337662
  }
337614
337663
  async decorateBrowserUseReceipt(input2, context, runtimeKey) {
@@ -340938,7 +340987,7 @@ async function runAgentKernel(agent) {
340938
340987
  return;
340939
340988
  }
340940
340989
  const publicError = normalizePublicProviderError(error, [currentAgent.activeModelConfig()?.api_key]);
340941
- if (/\b402\b|insufficient balance|insufficient funds|payment required|余额不足/i.test(publicError)) {
340990
+ if (/\b402\b|insufficient balance|insufficient funds|payment required|insufficient[_ -]?quota|quota (?:exceeded|exhausted)|credit(?:s| balance)? (?:exhausted|depleted)|billing hard limit|budget exhausted|余额不足|额度不足|配额(?:不足|耗尽|超限)/i.test(publicError)) {
340942
340991
  currentAgent.noteProviderBalanceFailure();
340943
340992
  }
340944
340993
  const final = assistantMessage2(model, [{ type: "text", text: `[Error] ${publicError}` }], "error");
@@ -341508,10 +341557,12 @@ function toKernelTools(agent, definitions, provisioning) {
341508
341557
  throw new Error(rawText);
341509
341558
  }
341510
341559
  const visionImage = visualFallbackImageInput(agent, name50, rawText);
341560
+ const capturedInput = name50 === "screen_capture" ? agent.registerCapturedImageInput(visionImage.image || "", "active-screenshot.jpg") : null;
341511
341561
  const directImage = imageInspectDataUrl(name50, rawText);
341512
- const text = spillOversizedToolResult(agent, name50, sanitizeVisualToolText(name50, rawText));
341562
+ const text = spillOversizedToolResult(agent, name50, capturedImageToolText(name50, sanitizeVisualToolText(name50, rawText), capturedInput?.id));
341513
341563
  const content = [{ type: "text", text }];
341514
- if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341564
+ if (name50 === "screen_capture" && capturedInput?.dataUrl) content.push({ type: "image", image: capturedInput.dataUrl, mimeType: capturedInput.mimeType });
341565
+ else if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341515
341566
  else if (visionImage.image) content.push({ type: "image", image: visionImage.image, mimeType: visionImage.mimeType });
341516
341567
  if (directImage) content.push({ type: "image", image: directImage, mimeType: "image/png" });
341517
341568
  const terminate = shouldTerminateAfterToolResult(name50) && (name50 !== "question" || agent.pendingOptions.length > 0);
@@ -341524,7 +341575,7 @@ function toKernelTools(agent, definitions, provisioning) {
341524
341575
  } catch {
341525
341576
  }
341526
341577
  }
341527
- return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, displayImage }, terminate };
341578
+ return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, capturedAttachmentId: capturedInput?.id, displayImage }, terminate };
341528
341579
  }
341529
341580
  };
341530
341581
  }).filter((tool) => !!tool.name);
@@ -341548,7 +341599,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341548
341599
  function spillOversizedToolResult(agent, name50, text) {
341549
341600
  const value = String(text || "");
341550
341601
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS) return value;
341551
- if (["computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_create", "SubAgent", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341602
+ if (["screen_capture", "computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_create", "SubAgent", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341552
341603
  return value;
341553
341604
  }
341554
341605
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341563,10 +341614,10 @@ function spillOversizedToolResult(agent, name50, text) {
341563
341614
  ].join("\n");
341564
341615
  }
341565
341616
  function sanitizeVisualToolText(name50, text) {
341566
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341617
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341567
341618
  try {
341568
341619
  const parsed = JSON.parse(text);
341569
- if (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341620
+ if (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341570
341621
  delete parsed.vision_image_path;
341571
341622
  delete parsed.vision_image_data_url;
341572
341623
  if (name50 === "pdf_read" && parsed.result && typeof parsed.result === "object") {
@@ -341582,7 +341633,7 @@ function sanitizeVisualToolText(name50, text) {
341582
341633
  }
341583
341634
  }
341584
341635
  function discardComputerUseVisionImage(name50, text) {
341585
- if (name50 !== "computer_use") return;
341636
+ if (name50 !== "screen_capture" && name50 !== "computer_use") return;
341586
341637
  try {
341587
341638
  const parsed = JSON.parse(text);
341588
341639
  const screenshotPath = String(parsed.vision_image_path || "");
@@ -341600,8 +341651,20 @@ function imageInspectDataUrl(name50, text) {
341600
341651
  return "";
341601
341652
  }
341602
341653
  }
341654
+ function capturedImageToolText(name50, text, attachmentId) {
341655
+ if (name50 !== "screen_capture" || !attachmentId) return text;
341656
+ try {
341657
+ const parsed = JSON.parse(text);
341658
+ parsed.attachment_id = attachmentId;
341659
+ parsed.image_input_channel = "user-image";
341660
+ parsed.inspect_next = { tool: "image_inspect", actions: ["source_info", "crop"], max_scale: 4 };
341661
+ return JSON.stringify(parsed, null, 2);
341662
+ } catch {
341663
+ return text;
341664
+ }
341665
+ }
341603
341666
  function visualFallbackImageInput(agent, name50, text) {
341604
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341667
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341605
341668
  const model = agent.activeModelConfig();
341606
341669
  if (!model?.vision) return {};
341607
341670
  try {
@@ -341727,7 +341790,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341727
341790
  actorId: agent.runtimeActorId,
341728
341791
  workspaceId: terminalTakeoverWorkspaceId(wsDir),
341729
341792
  backend: process.env.NEWMARK_WSL_DISTRO ? "wsl" : process.platform === "win32" ? "windows" : process.platform,
341730
- allowEphemeralVisionImage: (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341793
+ allowEphemeralVisionImage: (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341731
341794
  signal
341732
341795
  });
341733
341796
  if (signal?.aborted) throw abortError4();
@@ -342992,8 +343055,8 @@ function classifyRouteFailure(error) {
342992
343055
  if (statusCode === 400 || /bad request|invalid parameter|invalid request/i.test(text)) {
342993
343056
  return { type: "invalid_request", retryable: false, switchAllowed: false, statusCode };
342994
343057
  }
342995
- if (statusCode === 402 || /insufficient balance|insufficient funds|payment required|余额不足/i.test(text)) {
342996
- return { type: "balance_exhausted", retryable: false, switchAllowed: false, statusCode: 402 };
343058
+ if (statusCode === 402 || /insufficient balance|insufficient funds|payment required|insufficient[_ -]?quota|quota (?:exceeded|exhausted)|credit(?:s| balance)? (?:exhausted|depleted)|billing hard limit|budget exhausted|余额不足|额度不足|配额(?:不足|耗尽|超限)/i.test(text)) {
343059
+ return { type: "balance_exhausted", retryable: false, switchAllowed: true, statusCode: statusCode || 402 };
342997
343060
  }
342998
343061
  if (statusCode === 429 || /rate limit|too many requests/i.test(text)) {
342999
343062
  return { type: "rate_limited", retryable: true, switchAllowed: true, statusCode: 429, retryAfterMs };
@@ -343131,13 +343194,13 @@ var AutoRouter = class {
343131
343194
  }
343132
343195
  planAttempts(decision, candidates, failure) {
343133
343196
  const current = decision.resolvedDeployment;
343134
- if (!current || !failure.error.retryable || !failure.error.switchAllowed || failure.streamCommitted || failure.sideEffectCommitted) return [];
343197
+ if (!current || !failure.error.switchAllowed || failure.streamCommitted || failure.sideEffectCommitted) return [];
343135
343198
  const remainingAttempts = Math.max(0, 3 - decision.attempts.length);
343136
343199
  if (!remainingAttempts) return [];
343137
343200
  const attempts = [];
343138
343201
  const retryDelayMs = failure.error.retryAfterMs ?? 250;
343139
343202
  const alreadyRetriedCurrent = decision.attempts.some((attempt) => attempt.kind === "retry_same_deployment" && sameDeployment(attempt.deployment, current));
343140
- if (!alreadyRetriedCurrent && retryDelayMs <= (decision.retryBudgetMs ?? 5e3)) {
343203
+ if (failure.error.retryable && !alreadyRetriedCurrent && retryDelayMs <= (decision.retryBudgetMs ?? 5e3)) {
343141
343204
  attempts.push({
343142
343205
  deployment: { ...current },
343143
343206
  kind: "retry_same_deployment",
@@ -343158,11 +343221,16 @@ var AutoRouter = class {
343158
343221
  const eligible = candidates.filter((candidate) => candidate.enabled && inScope(candidate.deployment, scope) && inSubset(candidate.deployment, subset) && !sameDeployment(candidate.deployment, current) && !attemptedDeployments.some((attempted) => sameDeployment(candidate.deployment, attempted)) && validationEligible(candidate, now2).length === 0 && this.passedInitialHardFilters(decision, candidate) && this.circuitState(candidate.deployment, now2, false) !== "open");
343159
343222
  const equivalent = currentGroup ? eligible.find((candidate) => candidate.deployment.logicalModelGroupId === currentGroup && !candidate.fallbackOnly) : void 0;
343160
343223
  const fallback = eligible.find((candidate) => candidate.fallbackOnly);
343161
- for (const next of [equivalent, fallback]) {
343224
+ const rankedAlternates = decision.rankedCandidates.map((ranked) => eligible.find((candidate) => sameDeployment(candidate.deployment, ranked.deployment))).filter((candidate) => !!candidate && candidate !== equivalent && candidate !== fallback && !candidate.fallbackOnly);
343225
+ for (const candidate of eligible) {
343226
+ if (candidate === equivalent || candidate === fallback || candidate.fallbackOnly || rankedAlternates.some((existing) => sameDeployment(existing.deployment, candidate.deployment))) continue;
343227
+ rankedAlternates.push(candidate);
343228
+ }
343229
+ for (const next of [equivalent, fallback, ...rankedAlternates]) {
343162
343230
  if (!next || attempts.length >= 2) continue;
343163
343231
  attempts.push({
343164
343232
  deployment: { ...next.deployment },
343165
- kind: next === equivalent ? "equivalent_deployment" : "fallback_model",
343233
+ kind: next === equivalent ? "equivalent_deployment" : next === fallback ? "fallback_model" : "alternate_model",
343166
343234
  status: "planned",
343167
343235
  errorType: failure.error.type,
343168
343236
  streamCommitted: false,
@@ -343405,7 +343473,7 @@ function percentile(values, fraction) {
343405
343473
  }
343406
343474
  function failureFromType(type) {
343407
343475
  const retryable = type === "timeout" || type === "rate_limited" || type === "transport" || type === "server_error" || type === "empty_response";
343408
- return { type, retryable, switchAllowed: retryable };
343476
+ return { type, retryable, switchAllowed: retryable || type === "balance_exhausted" };
343409
343477
  }
343410
343478
 
343411
343479
  // src/context/services/agent-context-manager.ts
@@ -344870,6 +344938,7 @@ var Agent4 = class _Agent {
344870
344938
  memoryLabRebuildState = "idle";
344871
344939
  memoryLabRebuildError = "";
344872
344940
  displayImageDescriptionCache = /* @__PURE__ */ new Map();
344941
+ latestCapturedImageInputs = [];
344873
344942
  activeProcessAbortController = null;
344874
344943
  automationManager = null;
344875
344944
  activeAgentKernelRuntime = null;
@@ -345510,7 +345579,9 @@ var Agent4 = class _Agent {
345510
345579
  }
345511
345580
  updateProviders(value) {
345512
345581
  const before = this.config.providers();
345513
- this.config.set("models", "providers", mergeProviderSecrets(value, before));
345582
+ const merged = mergeProviderSecrets(value, before);
345583
+ resetEditedModelValidationEvidence(merged, before);
345584
+ this.config.set("models", "providers", merged);
345514
345585
  const after = this.config.providers();
345515
345586
  const beforeById = new Map(before.map((provider) => [provider.id, provider]));
345516
345587
  const afterById = new Map(after.map((provider) => [provider.id, provider]));
@@ -347067,6 +347138,13 @@ ${String(event.toolArgs || "")}`;
347067
347138
  listConversationStates() {
347068
347139
  return this.listWorkspaceConversationStates(this.workspace.current);
347069
347140
  }
347141
+ registerCapturedImageInput(dataUrl, name50 = "active-screenshot.jpg") {
347142
+ if (!String(dataUrl || "").startsWith("data:image/")) return null;
347143
+ const prepared = this.prepareSubmittedConversationImages([{ dataUrl, name: name50, type: dataUrl.slice(5, dataUrl.indexOf(";")) }]);
347144
+ const attachment = prepared.attachments[0] || null;
347145
+ this.latestCapturedImageInputs = attachment ? [attachment] : [];
347146
+ return attachment;
347147
+ }
347070
347148
  subagentConcurrencyLimit() {
347071
347149
  return this.intelligence === "ultra" ? 16 : 4;
347072
347150
  }
@@ -347589,6 +347667,19 @@ Review this persisted peer result and summarize or continue the parent task as n
347589
347667
  this.writeStoredConversationState(stored, targetWs);
347590
347668
  return true;
347591
347669
  }
347670
+ reorderConversationContinuations(orderedIds) {
347671
+ const currentIds = this.continuations.filter((item) => item.queueMode === "followUp" && !!item.clientMessageId).map((item) => String(item.clientMessageId));
347672
+ const completeOrder = orderedIds.length === currentIds.length && new Set(orderedIds).size === orderedIds.length && orderedIds.every((id) => currentIds.includes(id));
347673
+ if (!completeOrder) throw new Error("A complete queue order with unique current item ids is required");
347674
+ const byId = new Map(this.continuations.flatMap((item) => item.queueMode === "followUp" && item.clientMessageId ? [[String(item.clientMessageId), item]] : []));
347675
+ let nextIndex = 0;
347676
+ this.continuations = this.continuations.map((item) => {
347677
+ if (item.queueMode !== "followUp" || !item.clientMessageId) return item;
347678
+ return byId.get(orderedIds[nextIndex++]);
347679
+ });
347680
+ this.saveWorkspaceConversationState(true);
347681
+ return this.conversationContinuations();
347682
+ }
347592
347683
  renameConversation(id, title, ws = this.workspace.current) {
347593
347684
  const targetWs = ws || this.workspace.current;
347594
347685
  if (!targetWs) return false;
@@ -350394,6 +350485,9 @@ ${msg.content}
350394
350485
  const fallbackEnabled = this.config.getBool("models", "fallback_on_unavailable");
350395
350486
  const observedFailure = classifyRouteFailure(errorText);
350396
350487
  const observedDeployment = this.activeDeployment();
350488
+ if (observedFailure.type === "balance_exhausted" && observedDeployment) {
350489
+ this.providerBalanceBlockedUntilByDeployment.set(deploymentIdentity(observedDeployment), Date.now() + 5 * 6e4);
350490
+ }
350397
350491
  const previousAttempt = observedDeployment && this.lastRouteDecision ? [...this.lastRouteDecision.attempts].reverse().find((attempt) => deploymentIdentity(attempt.deployment) === deploymentIdentity(observedDeployment)) : void 0;
350398
350492
  if (this.model === "auto" && this.routeAttemptStartedAt === 0 && (this.lastRouteDecision?.finalStatus === "failed" || this.lastRouteDecision?.finalStatus === "blocked") && previousAttempt?.status === "failed" && previousAttempt.errorType === observedFailure.type) {
350399
350493
  return null;
@@ -350444,14 +350538,12 @@ ${msg.content}
350444
350538
  return current2.modelId;
350445
350539
  }
350446
350540
  if (!fallbackEnabled) return null;
350447
- if (!observedFailure.retryable || !observedFailure.switchAllowed || this.routeStreamCommitted || this.routeSideEffectCommitted) return null;
350541
+ if (!observedFailure.switchAllowed || this.routeStreamCommitted || this.routeSideEffectCommitted) return null;
350448
350542
  const current = this.model;
350449
- const all = this.scopedSwitchModels(current).filter((m2) => m2.name !== current);
350543
+ const currentDeployment = observedDeployment;
350544
+ const all = this.scopedSwitchModels(current).filter((m2) => !currentDeployment || deploymentIdentity(this.deploymentRef(m2)) !== deploymentIdentity(currentDeployment));
350450
350545
  if (!all.length) return null;
350451
- const usable = all.filter((m2) => {
350452
- const status = String(m2.evaluation?.status || "unknown").toLowerCase();
350453
- return status !== "unavailable" && !status.startsWith("error");
350454
- });
350546
+ const usable = all.filter((m2) => !this.isBalanceBlockedDeployment(this.deploymentRef(m2)) && !modelConfigIsUnavailable(m2));
350455
350547
  if (!usable.length) return null;
350456
350548
  const pref = this.config.autoSwitchPreference();
350457
350549
  const ranked = [...usable].sort((a3, b2) => this.modelScore(b2, pref, false, false) - this.modelScore(a3, pref, false, false));
@@ -351127,7 +351219,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351127
351219
  throw e3;
351128
351220
  }
351129
351221
  const msg = e3 instanceof Error ? e3.message : String(e3);
351130
- if (/\b402\b|insufficient balance|insufficient funds|payment required|余额不足/i.test(msg)) {
351222
+ if (/\b402\b|insufficient balance|insufficient funds|payment required|insufficient[_ -]?quota|quota (?:exceeded|exhausted)|credit(?:s| balance)? (?:exhausted|depleted)|billing hard limit|budget exhausted|余额不足|额度不足|配额(?:不足|耗尽|超限)/i.test(msg)) {
351131
351223
  this.noteProviderBalanceFailure();
351132
351224
  }
351133
351225
  this.status = "error";
@@ -351984,6 +352076,13 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
351984
352076
  }
351985
352077
  latestSubmittedImages(attachmentId = "") {
351986
352078
  const normalizedId = String(attachmentId || "").trim();
352079
+ const captured = hydrateConversationImageAttachments(this.rootPath, this.latestCapturedImageInputs).flatMap((attachment) => attachment.dataUrl ? [{ id: attachment.id, dataUrl: attachment.dataUrl }] : []);
352080
+ if (normalizedId) {
352081
+ const capturedMatch = captured.find((item) => item.id === normalizedId);
352082
+ if (capturedMatch) return [capturedMatch];
352083
+ } else if (captured.length) {
352084
+ return captured;
352085
+ }
351987
352086
  for (let index = this.chatMessages.length - 1; index >= 0; index -= 1) {
351988
352087
  const message = this.chatMessages[index];
351989
352088
  if (message?.role !== "user") continue;
@@ -353028,11 +353127,63 @@ function routeProviderFingerprint(provider) {
353028
353127
  enabled: provider.enabled,
353029
353128
  models: (provider.models || []).map((model) => ({
353030
353129
  name: model.name,
353130
+ display: model.display,
353131
+ description: model.description,
353132
+ maxTokens: model.max_tokens,
353133
+ vision: model.vision,
353134
+ thinking: !!model.thinking,
353135
+ imageOutput: !!model.image_output,
353031
353136
  enabled: model.enabled !== false,
353032
- logicalModelGroupId: model.logical_model_group_id || ""
353137
+ preview: !!model.preview,
353138
+ logicalModelGroupId: model.logical_model_group_id || "",
353139
+ privacy: model.privacy || [],
353140
+ capabilities: model.capabilities || [],
353141
+ supportedParameters: model.supported_parameters || [],
353142
+ routePreference: model.route_preference,
353143
+ fallbackOnly: !!model.fallback_only,
353144
+ thinkingTierMap: model.thinking_tier_map || {}
353033
353145
  }))
353034
353146
  });
353035
353147
  }
353148
+ function modelConfigurationFingerprint(model) {
353149
+ const { validation, evaluation, _previous_name, previous_name, ...configuration } = model;
353150
+ void validation;
353151
+ void evaluation;
353152
+ void _previous_name;
353153
+ void previous_name;
353154
+ return JSON.stringify(configuration);
353155
+ }
353156
+ function resetEditedModelValidationEvidence(incomingProviders, existingProviders) {
353157
+ const existingById = new Map(existingProviders.map((provider) => [provider.id, provider]));
353158
+ const existingByName = new Map(existingProviders.map((provider) => [provider.name, provider]));
353159
+ for (const rawProvider of incomingProviders) {
353160
+ if (!rawProvider || typeof rawProvider !== "object" || Array.isArray(rawProvider)) continue;
353161
+ const provider = rawProvider;
353162
+ const previousProvider = existingById.get(String(provider.id || "")) || existingByName.get(String(provider._previous_name || provider.previous_name || provider.name || ""));
353163
+ const models = Array.isArray(provider.models) ? provider.models : [];
353164
+ const providerConnectionChanged = !!previousProvider && (String(provider.base_url || provider.endpoint || "") !== previousProvider.base_url || String(provider.protocol || "") !== previousProvider.protocol);
353165
+ for (const rawModel of models) {
353166
+ if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
353167
+ const model = rawModel;
353168
+ const previousName = String(model._previous_name || model.previous_name || model.name || "");
353169
+ const previousModel = previousProvider?.models.find((candidate) => candidate.name === previousName);
353170
+ const edited = providerConnectionChanged || !!previousModel && modelConfigurationFingerprint(model) !== modelConfigurationFingerprint(previousModel);
353171
+ delete model._previous_name;
353172
+ delete model.previous_name;
353173
+ if (!edited) continue;
353174
+ model.validation = { level: "discovered", status: "degraded", checked_at: "", capabilities: {} };
353175
+ delete model.evaluation;
353176
+ model.speed_rating = "unknown";
353177
+ model.capability_rating = "unknown";
353178
+ }
353179
+ }
353180
+ }
353181
+ function modelConfigIsUnavailable(model) {
353182
+ const validationStatus = effectiveModelValidationStatus(model);
353183
+ if (model.validation?.level !== "discovered" && (validationStatus === "unavailable" || validationStatus === "auth_error" || validationStatus === "invalid_config")) return true;
353184
+ const evaluationStatus = validationStatus === "degraded" ? "degraded" : String(model.evaluation?.status || "").toLowerCase();
353185
+ return evaluationStatus === "unavailable" || evaluationStatus.startsWith("error");
353186
+ }
353036
353187
  function parseDeploymentSelectionValue2(value) {
353037
353188
  const marker = String(value || "").trim();
353038
353189
  if (!marker.startsWith("deployment:")) return null;
@@ -353049,9 +353200,9 @@ function parseDeploymentSelectionValue2(value) {
353049
353200
  function effectiveModelValidationStatus(model) {
353050
353201
  const raw = String(model.validation?.status || "").toLowerCase();
353051
353202
  if (raw === "auth_error") return raw;
353203
+ if (String(model.validation?.level || "").toLowerCase() === "discovered") return "degraded";
353052
353204
  const textEvidence = model.validation?.capabilities?.text === true || model.validation?.capabilities?.text_input === true || model.validation?.capabilities?.text_output === true || model.evaluation?.text_input === true || model.evaluation?.text_output === true;
353053
353205
  if (textEvidence && raw === "unavailable") return "degraded";
353054
- if (!raw && String(model.validation?.level || "").toLowerCase() === "discovered") return "degraded";
353055
353206
  return ["verified", "degraded", "unavailable", "auth_error", "rate_limited", "invalid_config"].includes(raw) ? raw : "unavailable";
353056
353207
  }
353057
353208
  function routeToolIsReadOnly(name50, rawArgs) {
@@ -353168,6 +353319,184 @@ var ConversationKernel = class {
353168
353319
  followUp: queued?.followUp.slice() || []
353169
353320
  };
353170
353321
  }
353322
+ queueItems(target) {
353323
+ const runtime = this.findRuntime(target);
353324
+ if (!runtime) return [];
353325
+ return runtime.pendingNextTurn.flatMap((item) => {
353326
+ if (item.queueMode !== "followUp") return [];
353327
+ if (typeof item.message === "string") return [];
353328
+ const id = String(item.message.clientMessageId || "");
353329
+ if (!id) return [];
353330
+ return [{
353331
+ id,
353332
+ text: String(item.message.visibleUserInput || item.message.text || "").replace(/^\[Next queued while current turn is running\]\n/, ""),
353333
+ queueMode: item.queueMode,
353334
+ requestedMode: item.message.visibleMode,
353335
+ goalObjective: item.message.goalObjective,
353336
+ runId: item.message.runId,
353337
+ createdAt: String(item.message.createdAt || "")
353338
+ }];
353339
+ });
353340
+ }
353341
+ enqueueNext(target, input2) {
353342
+ const runtime = this.findRuntime(target);
353343
+ if (!runtime?.activePromise || !runtime.runId) throw new Error("Target conversation is not running");
353344
+ const id = String(input2.id || "").trim().slice(0, 200);
353345
+ const text = String(input2.text || "").trim();
353346
+ if (!id || !text) throw new Error("Queue item id and text are required");
353347
+ const existing = this.queueItems(runtime.target).find((item) => item.id === id);
353348
+ if (existing) return existing;
353349
+ const prompt = `[Next queued while current turn is running]
353350
+ ${text}`;
353351
+ const createdAt = String(input2.createdAt || (/* @__PURE__ */ new Date()).toISOString());
353352
+ runtime.pendingNextTurn.push({
353353
+ message: {
353354
+ text: prompt,
353355
+ visibleUserInput: text,
353356
+ visibleMode: String(input2.requestedMode || "build"),
353357
+ goalObjective: String(input2.goalObjective || "") || void 0,
353358
+ clientMessageId: id,
353359
+ runId: runtime.runId,
353360
+ createdAt
353361
+ },
353362
+ queueMode: "followUp"
353363
+ });
353364
+ runtime.runner.retainConversationContinuations([{
353365
+ content: prompt,
353366
+ queueMode: "followUp",
353367
+ clientMessageId: id,
353368
+ runId: runtime.runId,
353369
+ createdAt
353370
+ }]);
353371
+ this.trackQueuedMessage(runtime, prompt, "followUp");
353372
+ this.emitQueueUpdate(runtime);
353373
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353374
+ }
353375
+ updateQueueItem(target, idInput, textInput) {
353376
+ const runtime = this.findRuntime(target);
353377
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353378
+ const id = String(idInput || "").trim();
353379
+ const text = String(textInput || "").trim();
353380
+ if (!id || !text) throw new Error("Queue item id and text are required");
353381
+ const pending3 = runtime.pendingNextTurn.find((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353382
+ if (!pending3 || typeof pending3.message === "string") throw new Error("Queue item is no longer editable");
353383
+ const oldPrompt = pending3.message.text;
353384
+ pending3.message.text = `[Next queued while current turn is running]
353385
+ ${text}`;
353386
+ pending3.message.visibleUserInput = text;
353387
+ runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending3.queueMode, clientMessageId: id });
353388
+ runtime.runner.retainConversationContinuations([{
353389
+ content: pending3.message.text,
353390
+ queueMode: pending3.queueMode,
353391
+ clientMessageId: id,
353392
+ runId: pending3.message.runId
353393
+ }]);
353394
+ this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending3.message.text, pending3.queueMode);
353395
+ this.emitQueueUpdate(runtime);
353396
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353397
+ }
353398
+ deleteQueueItem(target, idInput) {
353399
+ const runtime = this.findRuntime(target);
353400
+ if (!runtime) return false;
353401
+ const id = String(idInput || "").trim();
353402
+ const index = runtime.pendingNextTurn.findIndex((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353403
+ if (index < 0) return false;
353404
+ const [removed] = runtime.pendingNextTurn.splice(index, 1);
353405
+ if (typeof removed.message !== "string") {
353406
+ runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
353407
+ this.consumeQueuedMessage(runtime, removed.message.text);
353408
+ }
353409
+ this.emitQueueUpdate(runtime);
353410
+ return true;
353411
+ }
353412
+ reorderQueueItems(target, orderedIdsInput) {
353413
+ const runtime = this.findRuntime(target);
353414
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353415
+ const currentItems = this.queueItems(runtime.target);
353416
+ const orderedIds = Array.isArray(orderedIdsInput) ? orderedIdsInput.map((id) => String(id || "").trim()) : [];
353417
+ const currentIds = currentItems.map((item) => item.id);
353418
+ const completeOrder = orderedIds.length === currentIds.length && new Set(orderedIds).size === orderedIds.length && orderedIds.every((id) => currentIds.includes(id));
353419
+ if (!completeOrder) throw new Error("A complete queue order with unique current item ids is required");
353420
+ const persistedIds = runtime.runner.conversationContinuations().filter((item) => item.queueMode === "followUp" && !!item.clientMessageId).map((item) => String(item.clientMessageId));
353421
+ if (persistedIds.length !== currentIds.length || new Set(persistedIds).size !== persistedIds.length || persistedIds.some((id) => !currentIds.includes(id))) {
353422
+ throw new Error("Persisted queue does not match the complete queue order");
353423
+ }
353424
+ const pendingById = new Map(runtime.pendingNextTurn.flatMap((item) => {
353425
+ if (item.queueMode !== "followUp" || typeof item.message === "string" || !item.message.clientMessageId) return [];
353426
+ return [[String(item.message.clientMessageId), item]];
353427
+ }));
353428
+ const reorderedPending = orderedIds.map((id) => pendingById.get(id));
353429
+ let nextIndex = 0;
353430
+ runtime.pendingNextTurn = runtime.pendingNextTurn.map((item) => {
353431
+ if (item.queueMode !== "followUp" || typeof item.message === "string" || !item.message.clientMessageId) return item;
353432
+ return reorderedPending[nextIndex++];
353433
+ });
353434
+ runtime.runner.reorderConversationContinuations(orderedIds);
353435
+ runtime.queued.followUp = orderedIds.map((id) => pendingById.get(id)).map((item) => typeof item.message === "string" ? item.message : item.message.text);
353436
+ this.emitQueueUpdate(runtime);
353437
+ return this.queueItems(runtime.target);
353438
+ }
353439
+ setQueuePaused(target, paused) {
353440
+ const runtime = this.findRuntime(target);
353441
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353442
+ runtime.queuePaused = paused;
353443
+ this.emitQueueUpdate(runtime);
353444
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
353445
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
353446
+ }
353447
+ return runtime.queuePaused;
353448
+ }
353449
+ queueAction(target, action, input2 = {}) {
353450
+ const runtime = this.findRuntime(target);
353451
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353452
+ let receipt;
353453
+ if (action === "enqueue") {
353454
+ this.enqueueNext(runtime.target, {
353455
+ id: String(input2.id || ""),
353456
+ text: String(input2.text || ""),
353457
+ requestedMode: input2.requestedMode,
353458
+ goalObjective: input2.goalObjective,
353459
+ createdAt: input2.createdAt
353460
+ });
353461
+ } else if (action === "update") {
353462
+ this.updateQueueItem(runtime.target, String(input2.id || ""), String(input2.text || ""));
353463
+ } else if (action === "delete") {
353464
+ if (!this.deleteQueueItem(runtime.target, String(input2.id || ""))) throw new Error("Queue item was not found");
353465
+ } else if (action === "reorder") {
353466
+ this.reorderQueueItems(runtime.target, input2.orderedIds || []);
353467
+ } else if (action === "toggle_pause") {
353468
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
353469
+ } else if (action === "guide") {
353470
+ const item = this.queueItems(runtime.target).find((entry) => entry.id === String(input2.id || ""));
353471
+ if (!item) throw new Error("Queue item was not found");
353472
+ receipt = this.enqueueGuide({
353473
+ clientMessageId: item.id,
353474
+ guideId: (0, import_crypto16.randomUUID)(),
353475
+ target: runtime.target,
353476
+ runId: runtime.runId,
353477
+ deliveryMode: "steer",
353478
+ text: item.goalObjective ? `Goal for the current Build:
353479
+ ${item.goalObjective}` : item.text,
353480
+ goalObjective: item.goalObjective,
353481
+ createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString()
353482
+ });
353483
+ if (receipt.status === "rejected") return {
353484
+ ok: false,
353485
+ queueItems: this.queueItems(runtime.target),
353486
+ queuePaused: runtime.queuePaused,
353487
+ queued: this.queued(runtime.target),
353488
+ receipt
353489
+ };
353490
+ this.deleteQueueItem(runtime.target, item.id);
353491
+ }
353492
+ return {
353493
+ ok: true,
353494
+ queueItems: this.queueItems(runtime.target),
353495
+ queuePaused: runtime.queuePaused,
353496
+ queued: this.queued(runtime.target),
353497
+ receipt
353498
+ };
353499
+ }
353171
353500
  events(target) {
353172
353501
  return this.findRuntime(target)?.events.slice() || [];
353173
353502
  }
@@ -353196,6 +353525,8 @@ var ConversationKernel = class {
353196
353525
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
353197
353526
  target: normalized,
353198
353527
  queued: this.queued(normalized),
353528
+ queueItems: this.queueItems(normalized),
353529
+ queuePaused: runtime?.queuePaused === true,
353199
353530
  workEvents: this.events(normalized),
353200
353531
  runtime: this.runtimeState(normalized),
353201
353532
  mode: runner.mode,
@@ -353631,7 +353962,7 @@ var ConversationKernel = class {
353631
353962
  if (runtime.stopRequestedRunId === runId) {
353632
353963
  stopped = true;
353633
353964
  this.settleCooperativeStop(runtime, runId);
353634
- } else if (runtime.pendingNextTurn.length > 0) {
353965
+ } else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353635
353966
  this.schedulePendingRuntimeContinuation(runtime, runId);
353636
353967
  }
353637
353968
  }
@@ -353666,7 +353997,7 @@ var ConversationKernel = class {
353666
353997
  return this.result(runtime, lastTokens);
353667
353998
  }
353668
353999
  for (; ; ) {
353669
- while (runtime.pendingNextTurn.length > 0) {
354000
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353670
354001
  if (runtime.stopRequestedRunId === runtime.runId) return this.result(runtime, lastTokens);
353671
354002
  const next = runtime.pendingNextTurn.shift();
353672
354003
  if (next.queueMode === "steer" && typeof next.message !== "string" && !!next.message.clientMessageId) {
@@ -353708,11 +354039,11 @@ ${batchText}`,
353708
354039
  this.mirrorHostIfTargetActive(runtime);
353709
354040
  return this.result(runtime, lastTokens);
353710
354041
  }
353711
- this.clearQueued(runtime);
354042
+ if (!runtime.pendingNextTurn.length) this.clearQueued(runtime);
353712
354043
  const completedRunId = runtime.runId;
353713
354044
  runtime.runner.finishConversationWorkRun(completedRunId, "completed");
353714
354045
  await new Promise((resolve16) => setImmediate(resolve16));
353715
- if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && runtime.pendingNextTurn.length > 0) {
354046
+ if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353716
354047
  runtime.guideAcceptanceClosedRunId = "";
353717
354048
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
353718
354049
  throw new Error(`Unable to resume deferred Guide work run ${completedRunId}`);
@@ -353803,6 +354134,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353803
354134
  events: [],
353804
354135
  pendingNextTurn: [],
353805
354136
  queued: { steering: [], followUp: [] },
354137
+ queuePaused: false,
353806
354138
  runId: "",
353807
354139
  generation: this.generations.get(target.runtimeKey) || 0,
353808
354140
  stopRequestedRunId: "",
@@ -353898,7 +354230,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353898
354230
  }
353899
354231
  setImmediate(() => {
353900
354232
  runtime.pendingContinuationRunId = void 0;
353901
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
354233
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused) return;
353902
354234
  const next = runtime.pendingNextTurn.shift();
353903
354235
  if (!next) return;
353904
354236
  const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
@@ -354049,7 +354381,36 @@ Review this persisted peer result and summarize or continue the parent task as n
354049
354381
  const text = typeof message === "string" ? message : message.text;
354050
354382
  const prompt = isSteer ? text : `[Next queued while current turn is running]
354051
354383
  ${text}`;
354052
- if (!isSteer) this.trackQueuedMessage(runtime, prompt, queueMode);
354384
+ if (!isSteer) {
354385
+ const structured = typeof message === "string" ? null : message;
354386
+ const clientMessageId = String(structured?.clientMessageId || (0, import_crypto16.randomUUID)());
354387
+ const queuedMessage = {
354388
+ ...structured || {},
354389
+ text: prompt,
354390
+ visibleUserInput: structured?.visibleUserInput || text,
354391
+ visibleMode: structured?.visibleMode || runtime.options.mode,
354392
+ clientMessageId,
354393
+ runId: structured?.runId || runtime.runId,
354394
+ createdAt: String(structured?.createdAt || (/* @__PURE__ */ new Date()).toISOString())
354395
+ };
354396
+ if (!runtime.pendingNextTurn.some((item) => typeof item.message !== "string" && item.message.clientMessageId === clientMessageId)) {
354397
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: "followUp" });
354398
+ }
354399
+ runtime.runner.retainConversationContinuations([{
354400
+ content: prompt,
354401
+ queueMode: "followUp",
354402
+ clientMessageId,
354403
+ runId: queuedMessage.runId,
354404
+ images: queuedMessage.images?.map((image) => ({ ...image })),
354405
+ attachments: queuedMessage.attachments?.map((attachment) => ({ ...attachment })),
354406
+ createdAt: queuedMessage.createdAt
354407
+ }]);
354408
+ this.trackQueuedMessage(runtime, prompt, "followUp");
354409
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId ? "Next message retained while stopping." : "Next message queued.");
354410
+ if (runtime.stopRequestedRunId === runtime.runId) runtime.forceStopArmedRunId = "";
354411
+ this.emitQueueUpdate(runtime);
354412
+ return;
354413
+ }
354053
354414
  if (runtime.stopRequestedRunId === runtime.runId) {
354054
354415
  runtime.forceStopArmedRunId = "";
354055
354416
  const structured = typeof message === "string" ? void 0 : message;
@@ -354095,6 +354456,12 @@ ${text}`;
354095
354456
  }
354096
354457
  if (changed) this.emitQueueUpdate(runtime);
354097
354458
  }
354459
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
354460
+ this.queueState(runtime);
354461
+ const list = queueMode === "steer" ? runtime.queued.steering : runtime.queued.followUp;
354462
+ const index = list.indexOf(oldMessage);
354463
+ if (index >= 0) list[index] = nextMessage;
354464
+ }
354098
354465
  emitQueueUpdate(runtime) {
354099
354466
  this.queueState(runtime);
354100
354467
  runtime.runner.emitWorkEvent({
@@ -354216,9 +354583,10 @@ ${text}`;
354216
354583
  const runId = runtime.runId;
354217
354584
  await Promise.resolve();
354218
354585
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId) return true;
354219
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) return false;
354586
+ const hasSteering = runtime.pendingNextTurn.some((item) => item.queueMode === "steer");
354587
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0) return false;
354220
354588
  runtime.guideAcceptanceClosedRunId = runId;
354221
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
354589
+ if (runtime.pendingNextTurn.some((item) => item.queueMode === "steer") || runtime.runner.subagents.readRootInbox().length > 0) {
354222
354590
  runtime.guideAcceptanceClosedRunId = "";
354223
354591
  return false;
354224
354592
  }
@@ -354429,6 +354797,9 @@ async function handle(request) {
354429
354797
  target
354430
354798
  });
354431
354799
  }
354800
+ if (request.method === "queue_action") {
354801
+ return kernel.queueAction(requestTarget(request.params), request.params.action, request.params.input);
354802
+ }
354432
354803
  if (request.method === "checkpoint") return kernel.checkpoint(requestTarget(request.params));
354433
354804
  if (request.method === "context_compress") {
354434
354805
  return kernel.compressContext(requestTarget(request.params), request.params.options);