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.
@@ -327556,6 +327556,7 @@ var NATIVE_TOOL_CATALOG = [
327556
327556
  { name: "browser_reload", label: "Browser reload", description: "Reload the built-in browser.", category: "browser", defaultEnabled: true },
327557
327557
  { name: "browser_cdp", label: "Browser CDP", description: "Run an advanced Chrome DevTools Protocol command.", category: "browser", defaultEnabled: true },
327558
327558
  { 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 },
327559
+ { 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" },
327559
327560
  { name: "computer_use", label: "Computer Use", description: "Observe and control Windows desktop UI with screenshots and semantic objects.", category: "desktop", defaultEnabled: true },
327560
327561
  { 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" },
327561
327562
  { 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" },
@@ -335746,6 +335747,7 @@ var import_crypto6 = require("crypto");
335746
335747
  // src/core/toolPolicy.ts
335747
335748
  var REQUIRED_TOOLS = /* @__PURE__ */ new Set(["pwd", "read", "glob", "grep"]);
335748
335749
  var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335750
+ "screen_capture",
335749
335751
  "image_inspect",
335750
335752
  "image_display",
335751
335753
  "ocr_read",
@@ -335778,6 +335780,7 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335778
335780
  "branch_create"
335779
335781
  ]);
335780
335782
  var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335783
+ "screen_capture",
335781
335784
  "task_read",
335782
335785
  "pwd",
335783
335786
  "read",
@@ -335825,6 +335828,7 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335825
335828
  "git_status",
335826
335829
  "file_audit",
335827
335830
  "repo_security_audit",
335831
+ "screen_capture",
335828
335832
  "SubAgent"
335829
335833
  ]);
335830
335834
  function isConcurrencySafeTool(name50, riskLevel) {
@@ -336908,6 +336912,13 @@ var ToolExecutor = class {
336908
336912
  max_refs: { type: "number" },
336909
336913
  attribute: { type: "string" }
336910
336914
  }, ["action"]),
336915
+ 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.", {
336916
+ target: { type: "string", enum: ["desktop", "application"], description: "Capture the whole desktop or one visible application. Defaults to desktop." },
336917
+ app_target: { type: "string", description: "For target=application: application title, process name, process id, or window handle." },
336918
+ 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." },
336919
+ 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." },
336920
+ max_chars: { type: "number", minimum: 1e3, maximum: 1e5, description: "Maximum returned semantic UI text characters." }
336921
+ }, []),
336911
336922
  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.", {
336912
336923
  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"] },
336913
336924
  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." },
@@ -337398,6 +337409,44 @@ var ToolExecutor = class {
337398
337409
  result: parsed
337399
337410
  }, null, 2);
337400
337411
  }
337412
+ case "screen_capture": {
337413
+ const target = g2("target").toLowerCase() === "application" ? "application" : "desktop";
337414
+ if (process.env.NEWMARK_WSL_DISTRO) {
337415
+ const result = await requestWindowsHostTool("screen_capture", args, {
337416
+ conversationId: context.conversationId || "default",
337417
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337418
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337419
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
337420
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337421
+ mode: context.mode || "build"
337422
+ }, 12e4, context.signal);
337423
+ return typeof result === "string" ? result : JSON.stringify(result);
337424
+ }
337425
+ if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337426
+ const result = await requestUtilityHostTool("screen_capture", args, {
337427
+ conversationId: context.conversationId || "default",
337428
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337429
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337430
+ workspacePath: context.workspacePath || wsPath,
337431
+ backend: "utility",
337432
+ mode: context.mode || "build",
337433
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true
337434
+ }, 12e4, context.signal);
337435
+ return typeof result === "string" ? result : JSON.stringify(result);
337436
+ }
337437
+ return await runComputerUse({
337438
+ action: target === "application" ? "app_observe" : "observe",
337439
+ appTarget: g2("app_target"),
337440
+ windowHandle: target === "application" ? g2("app_target") : "",
337441
+ maxChars: Number(args.max_chars || 3e4),
337442
+ workspacePath: wsPath,
337443
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337444
+ captureMaxWidth: Number(args.capture_max_width),
337445
+ captureMaxHeight: Number(args.capture_max_height),
337446
+ invocation: context.invocation,
337447
+ ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`
337448
+ });
337449
+ }
337401
337450
  case "computer_use": {
337402
337451
  const action = normalizeComputerUseAction(g2("action"));
337403
337452
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
@@ -337604,7 +337653,7 @@ var ToolExecutor = class {
337604
337653
  }
337605
337654
  hostSupportsTool(name50) {
337606
337655
  if (name50.startsWith("browser_") && !this.hostProfile.electronBrowser) return false;
337607
- if (name50 === "computer_use" && !this.hostProfile.windowsComputerUse) return false;
337656
+ if ((name50 === "computer_use" || name50 === "screen_capture") && !this.hostProfile.windowsComputerUse) return false;
337608
337657
  return true;
337609
337658
  }
337610
337659
  async decorateBrowserUseReceipt(input, context, runtimeKey) {
@@ -340934,7 +340983,7 @@ async function runAgentKernel(agent) {
340934
340983
  return;
340935
340984
  }
340936
340985
  const publicError = normalizePublicProviderError(error, [currentAgent.activeModelConfig()?.api_key]);
340937
- if (/\b402\b|insufficient balance|insufficient funds|payment required|余额不足/i.test(publicError)) {
340986
+ 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)) {
340938
340987
  currentAgent.noteProviderBalanceFailure();
340939
340988
  }
340940
340989
  const final = assistantMessage2(model, [{ type: "text", text: `[Error] ${publicError}` }], "error");
@@ -341504,10 +341553,12 @@ function toKernelTools(agent, definitions, provisioning) {
341504
341553
  throw new Error(rawText);
341505
341554
  }
341506
341555
  const visionImage = visualFallbackImageInput(agent, name50, rawText);
341556
+ const capturedInput = name50 === "screen_capture" ? agent.registerCapturedImageInput(visionImage.image || "", "active-screenshot.jpg") : null;
341507
341557
  const directImage = imageInspectDataUrl(name50, rawText);
341508
- const text = spillOversizedToolResult(agent, name50, sanitizeVisualToolText(name50, rawText));
341558
+ const text = spillOversizedToolResult(agent, name50, capturedImageToolText(name50, sanitizeVisualToolText(name50, rawText), capturedInput?.id));
341509
341559
  const content = [{ type: "text", text }];
341510
- if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341560
+ if (name50 === "screen_capture" && capturedInput?.dataUrl) content.push({ type: "image", image: capturedInput.dataUrl, mimeType: capturedInput.mimeType });
341561
+ else if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341511
341562
  else if (visionImage.image) content.push({ type: "image", image: visionImage.image, mimeType: visionImage.mimeType });
341512
341563
  if (directImage) content.push({ type: "image", image: directImage, mimeType: "image/png" });
341513
341564
  const terminate = shouldTerminateAfterToolResult(name50) && (name50 !== "question" || agent.pendingOptions.length > 0);
@@ -341520,7 +341571,7 @@ function toKernelTools(agent, definitions, provisioning) {
341520
341571
  } catch {
341521
341572
  }
341522
341573
  }
341523
- return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, displayImage }, terminate };
341574
+ return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, capturedAttachmentId: capturedInput?.id, displayImage }, terminate };
341524
341575
  }
341525
341576
  };
341526
341577
  }).filter((tool) => !!tool.name);
@@ -341544,7 +341595,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341544
341595
  function spillOversizedToolResult(agent, name50, text) {
341545
341596
  const value = String(text || "");
341546
341597
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS) return value;
341547
- 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)) {
341598
+ 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)) {
341548
341599
  return value;
341549
341600
  }
341550
341601
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341559,10 +341610,10 @@ function spillOversizedToolResult(agent, name50, text) {
341559
341610
  ].join("\n");
341560
341611
  }
341561
341612
  function sanitizeVisualToolText(name50, text) {
341562
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341613
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341563
341614
  try {
341564
341615
  const parsed = JSON.parse(text);
341565
- if (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341616
+ if (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341566
341617
  delete parsed.vision_image_path;
341567
341618
  delete parsed.vision_image_data_url;
341568
341619
  if (name50 === "pdf_read" && parsed.result && typeof parsed.result === "object") {
@@ -341578,7 +341629,7 @@ function sanitizeVisualToolText(name50, text) {
341578
341629
  }
341579
341630
  }
341580
341631
  function discardComputerUseVisionImage(name50, text) {
341581
- if (name50 !== "computer_use") return;
341632
+ if (name50 !== "screen_capture" && name50 !== "computer_use") return;
341582
341633
  try {
341583
341634
  const parsed = JSON.parse(text);
341584
341635
  const screenshotPath = String(parsed.vision_image_path || "");
@@ -341596,8 +341647,20 @@ function imageInspectDataUrl(name50, text) {
341596
341647
  return "";
341597
341648
  }
341598
341649
  }
341650
+ function capturedImageToolText(name50, text, attachmentId) {
341651
+ if (name50 !== "screen_capture" || !attachmentId) return text;
341652
+ try {
341653
+ const parsed = JSON.parse(text);
341654
+ parsed.attachment_id = attachmentId;
341655
+ parsed.image_input_channel = "user-image";
341656
+ parsed.inspect_next = { tool: "image_inspect", actions: ["source_info", "crop"], max_scale: 4 };
341657
+ return JSON.stringify(parsed, null, 2);
341658
+ } catch {
341659
+ return text;
341660
+ }
341661
+ }
341599
341662
  function visualFallbackImageInput(agent, name50, text) {
341600
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341663
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341601
341664
  const model = agent.activeModelConfig();
341602
341665
  if (!model?.vision) return {};
341603
341666
  try {
@@ -341723,7 +341786,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341723
341786
  actorId: agent.runtimeActorId,
341724
341787
  workspaceId: terminalTakeoverWorkspaceId(wsDir),
341725
341788
  backend: process.env.NEWMARK_WSL_DISTRO ? "wsl" : process.platform === "win32" ? "windows" : process.platform,
341726
- allowEphemeralVisionImage: (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341789
+ allowEphemeralVisionImage: (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341727
341790
  signal
341728
341791
  });
341729
341792
  if (signal?.aborted) throw abortError4();
@@ -342988,8 +343051,8 @@ function classifyRouteFailure(error) {
342988
343051
  if (statusCode === 400 || /bad request|invalid parameter|invalid request/i.test(text)) {
342989
343052
  return { type: "invalid_request", retryable: false, switchAllowed: false, statusCode };
342990
343053
  }
342991
- if (statusCode === 402 || /insufficient balance|insufficient funds|payment required|余额不足/i.test(text)) {
342992
- return { type: "balance_exhausted", retryable: false, switchAllowed: false, statusCode: 402 };
343054
+ 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)) {
343055
+ return { type: "balance_exhausted", retryable: false, switchAllowed: true, statusCode: statusCode || 402 };
342993
343056
  }
342994
343057
  if (statusCode === 429 || /rate limit|too many requests/i.test(text)) {
342995
343058
  return { type: "rate_limited", retryable: true, switchAllowed: true, statusCode: 429, retryAfterMs };
@@ -343127,13 +343190,13 @@ var AutoRouter = class {
343127
343190
  }
343128
343191
  planAttempts(decision, candidates, failure) {
343129
343192
  const current = decision.resolvedDeployment;
343130
- if (!current || !failure.error.retryable || !failure.error.switchAllowed || failure.streamCommitted || failure.sideEffectCommitted) return [];
343193
+ if (!current || !failure.error.switchAllowed || failure.streamCommitted || failure.sideEffectCommitted) return [];
343131
343194
  const remainingAttempts = Math.max(0, 3 - decision.attempts.length);
343132
343195
  if (!remainingAttempts) return [];
343133
343196
  const attempts = [];
343134
343197
  const retryDelayMs = failure.error.retryAfterMs ?? 250;
343135
343198
  const alreadyRetriedCurrent = decision.attempts.some((attempt) => attempt.kind === "retry_same_deployment" && sameDeployment(attempt.deployment, current));
343136
- if (!alreadyRetriedCurrent && retryDelayMs <= (decision.retryBudgetMs ?? 5e3)) {
343199
+ if (failure.error.retryable && !alreadyRetriedCurrent && retryDelayMs <= (decision.retryBudgetMs ?? 5e3)) {
343137
343200
  attempts.push({
343138
343201
  deployment: { ...current },
343139
343202
  kind: "retry_same_deployment",
@@ -343154,11 +343217,16 @@ var AutoRouter = class {
343154
343217
  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");
343155
343218
  const equivalent = currentGroup ? eligible.find((candidate) => candidate.deployment.logicalModelGroupId === currentGroup && !candidate.fallbackOnly) : void 0;
343156
343219
  const fallback = eligible.find((candidate) => candidate.fallbackOnly);
343157
- for (const next of [equivalent, fallback]) {
343220
+ const rankedAlternates = decision.rankedCandidates.map((ranked) => eligible.find((candidate) => sameDeployment(candidate.deployment, ranked.deployment))).filter((candidate) => !!candidate && candidate !== equivalent && candidate !== fallback && !candidate.fallbackOnly);
343221
+ for (const candidate of eligible) {
343222
+ if (candidate === equivalent || candidate === fallback || candidate.fallbackOnly || rankedAlternates.some((existing) => sameDeployment(existing.deployment, candidate.deployment))) continue;
343223
+ rankedAlternates.push(candidate);
343224
+ }
343225
+ for (const next of [equivalent, fallback, ...rankedAlternates]) {
343158
343226
  if (!next || attempts.length >= 2) continue;
343159
343227
  attempts.push({
343160
343228
  deployment: { ...next.deployment },
343161
- kind: next === equivalent ? "equivalent_deployment" : "fallback_model",
343229
+ kind: next === equivalent ? "equivalent_deployment" : next === fallback ? "fallback_model" : "alternate_model",
343162
343230
  status: "planned",
343163
343231
  errorType: failure.error.type,
343164
343232
  streamCommitted: false,
@@ -343401,7 +343469,7 @@ function percentile(values, fraction) {
343401
343469
  }
343402
343470
  function failureFromType(type) {
343403
343471
  const retryable = type === "timeout" || type === "rate_limited" || type === "transport" || type === "server_error" || type === "empty_response";
343404
- return { type, retryable, switchAllowed: retryable };
343472
+ return { type, retryable, switchAllowed: retryable || type === "balance_exhausted" };
343405
343473
  }
343406
343474
 
343407
343475
  // src/context/services/agent-context-manager.ts
@@ -344866,6 +344934,7 @@ var Agent4 = class _Agent {
344866
344934
  memoryLabRebuildState = "idle";
344867
344935
  memoryLabRebuildError = "";
344868
344936
  displayImageDescriptionCache = /* @__PURE__ */ new Map();
344937
+ latestCapturedImageInputs = [];
344869
344938
  activeProcessAbortController = null;
344870
344939
  automationManager = null;
344871
344940
  activeAgentKernelRuntime = null;
@@ -345506,7 +345575,9 @@ var Agent4 = class _Agent {
345506
345575
  }
345507
345576
  updateProviders(value) {
345508
345577
  const before = this.config.providers();
345509
- this.config.set("models", "providers", mergeProviderSecrets(value, before));
345578
+ const merged = mergeProviderSecrets(value, before);
345579
+ resetEditedModelValidationEvidence(merged, before);
345580
+ this.config.set("models", "providers", merged);
345510
345581
  const after = this.config.providers();
345511
345582
  const beforeById = new Map(before.map((provider) => [provider.id, provider]));
345512
345583
  const afterById = new Map(after.map((provider) => [provider.id, provider]));
@@ -347063,6 +347134,13 @@ ${String(event.toolArgs || "")}`;
347063
347134
  listConversationStates() {
347064
347135
  return this.listWorkspaceConversationStates(this.workspace.current);
347065
347136
  }
347137
+ registerCapturedImageInput(dataUrl, name50 = "active-screenshot.jpg") {
347138
+ if (!String(dataUrl || "").startsWith("data:image/")) return null;
347139
+ const prepared = this.prepareSubmittedConversationImages([{ dataUrl, name: name50, type: dataUrl.slice(5, dataUrl.indexOf(";")) }]);
347140
+ const attachment = prepared.attachments[0] || null;
347141
+ this.latestCapturedImageInputs = attachment ? [attachment] : [];
347142
+ return attachment;
347143
+ }
347066
347144
  subagentConcurrencyLimit() {
347067
347145
  return this.intelligence === "ultra" ? 16 : 4;
347068
347146
  }
@@ -347585,6 +347663,19 @@ Review this persisted peer result and summarize or continue the parent task as n
347585
347663
  this.writeStoredConversationState(stored, targetWs);
347586
347664
  return true;
347587
347665
  }
347666
+ reorderConversationContinuations(orderedIds) {
347667
+ const currentIds = this.continuations.filter((item) => item.queueMode === "followUp" && !!item.clientMessageId).map((item) => String(item.clientMessageId));
347668
+ const completeOrder = orderedIds.length === currentIds.length && new Set(orderedIds).size === orderedIds.length && orderedIds.every((id) => currentIds.includes(id));
347669
+ if (!completeOrder) throw new Error("A complete queue order with unique current item ids is required");
347670
+ const byId = new Map(this.continuations.flatMap((item) => item.queueMode === "followUp" && item.clientMessageId ? [[String(item.clientMessageId), item]] : []));
347671
+ let nextIndex = 0;
347672
+ this.continuations = this.continuations.map((item) => {
347673
+ if (item.queueMode !== "followUp" || !item.clientMessageId) return item;
347674
+ return byId.get(orderedIds[nextIndex++]);
347675
+ });
347676
+ this.saveWorkspaceConversationState(true);
347677
+ return this.conversationContinuations();
347678
+ }
347588
347679
  renameConversation(id, title, ws = this.workspace.current) {
347589
347680
  const targetWs = ws || this.workspace.current;
347590
347681
  if (!targetWs) return false;
@@ -350390,6 +350481,9 @@ ${msg.content}
350390
350481
  const fallbackEnabled = this.config.getBool("models", "fallback_on_unavailable");
350391
350482
  const observedFailure = classifyRouteFailure(errorText);
350392
350483
  const observedDeployment = this.activeDeployment();
350484
+ if (observedFailure.type === "balance_exhausted" && observedDeployment) {
350485
+ this.providerBalanceBlockedUntilByDeployment.set(deploymentIdentity(observedDeployment), Date.now() + 5 * 6e4);
350486
+ }
350393
350487
  const previousAttempt = observedDeployment && this.lastRouteDecision ? [...this.lastRouteDecision.attempts].reverse().find((attempt) => deploymentIdentity(attempt.deployment) === deploymentIdentity(observedDeployment)) : void 0;
350394
350488
  if (this.model === "auto" && this.routeAttemptStartedAt === 0 && (this.lastRouteDecision?.finalStatus === "failed" || this.lastRouteDecision?.finalStatus === "blocked") && previousAttempt?.status === "failed" && previousAttempt.errorType === observedFailure.type) {
350395
350489
  return null;
@@ -350440,14 +350534,12 @@ ${msg.content}
350440
350534
  return current2.modelId;
350441
350535
  }
350442
350536
  if (!fallbackEnabled) return null;
350443
- if (!observedFailure.retryable || !observedFailure.switchAllowed || this.routeStreamCommitted || this.routeSideEffectCommitted) return null;
350537
+ if (!observedFailure.switchAllowed || this.routeStreamCommitted || this.routeSideEffectCommitted) return null;
350444
350538
  const current = this.model;
350445
- const all = this.scopedSwitchModels(current).filter((m2) => m2.name !== current);
350539
+ const currentDeployment = observedDeployment;
350540
+ const all = this.scopedSwitchModels(current).filter((m2) => !currentDeployment || deploymentIdentity(this.deploymentRef(m2)) !== deploymentIdentity(currentDeployment));
350446
350541
  if (!all.length) return null;
350447
- const usable = all.filter((m2) => {
350448
- const status = String(m2.evaluation?.status || "unknown").toLowerCase();
350449
- return status !== "unavailable" && !status.startsWith("error");
350450
- });
350542
+ const usable = all.filter((m2) => !this.isBalanceBlockedDeployment(this.deploymentRef(m2)) && !modelConfigIsUnavailable(m2));
350451
350543
  if (!usable.length) return null;
350452
350544
  const pref = this.config.autoSwitchPreference();
350453
350545
  const ranked = [...usable].sort((a3, b2) => this.modelScore(b2, pref, false, false) - this.modelScore(a3, pref, false, false));
@@ -351123,7 +351215,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351123
351215
  throw e3;
351124
351216
  }
351125
351217
  const msg = e3 instanceof Error ? e3.message : String(e3);
351126
- if (/\b402\b|insufficient balance|insufficient funds|payment required|余额不足/i.test(msg)) {
351218
+ 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)) {
351127
351219
  this.noteProviderBalanceFailure();
351128
351220
  }
351129
351221
  this.status = "error";
@@ -351980,6 +352072,13 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
351980
352072
  }
351981
352073
  latestSubmittedImages(attachmentId = "") {
351982
352074
  const normalizedId = String(attachmentId || "").trim();
352075
+ const captured = hydrateConversationImageAttachments(this.rootPath, this.latestCapturedImageInputs).flatMap((attachment) => attachment.dataUrl ? [{ id: attachment.id, dataUrl: attachment.dataUrl }] : []);
352076
+ if (normalizedId) {
352077
+ const capturedMatch = captured.find((item) => item.id === normalizedId);
352078
+ if (capturedMatch) return [capturedMatch];
352079
+ } else if (captured.length) {
352080
+ return captured;
352081
+ }
351983
352082
  for (let index = this.chatMessages.length - 1; index >= 0; index -= 1) {
351984
352083
  const message = this.chatMessages[index];
351985
352084
  if (message?.role !== "user") continue;
@@ -353024,11 +353123,63 @@ function routeProviderFingerprint(provider) {
353024
353123
  enabled: provider.enabled,
353025
353124
  models: (provider.models || []).map((model) => ({
353026
353125
  name: model.name,
353126
+ display: model.display,
353127
+ description: model.description,
353128
+ maxTokens: model.max_tokens,
353129
+ vision: model.vision,
353130
+ thinking: !!model.thinking,
353131
+ imageOutput: !!model.image_output,
353027
353132
  enabled: model.enabled !== false,
353028
- logicalModelGroupId: model.logical_model_group_id || ""
353133
+ preview: !!model.preview,
353134
+ logicalModelGroupId: model.logical_model_group_id || "",
353135
+ privacy: model.privacy || [],
353136
+ capabilities: model.capabilities || [],
353137
+ supportedParameters: model.supported_parameters || [],
353138
+ routePreference: model.route_preference,
353139
+ fallbackOnly: !!model.fallback_only,
353140
+ thinkingTierMap: model.thinking_tier_map || {}
353029
353141
  }))
353030
353142
  });
353031
353143
  }
353144
+ function modelConfigurationFingerprint(model) {
353145
+ const { validation, evaluation, _previous_name, previous_name, ...configuration } = model;
353146
+ void validation;
353147
+ void evaluation;
353148
+ void _previous_name;
353149
+ void previous_name;
353150
+ return JSON.stringify(configuration);
353151
+ }
353152
+ function resetEditedModelValidationEvidence(incomingProviders, existingProviders) {
353153
+ const existingById = new Map(existingProviders.map((provider) => [provider.id, provider]));
353154
+ const existingByName = new Map(existingProviders.map((provider) => [provider.name, provider]));
353155
+ for (const rawProvider of incomingProviders) {
353156
+ if (!rawProvider || typeof rawProvider !== "object" || Array.isArray(rawProvider)) continue;
353157
+ const provider = rawProvider;
353158
+ const previousProvider = existingById.get(String(provider.id || "")) || existingByName.get(String(provider._previous_name || provider.previous_name || provider.name || ""));
353159
+ const models = Array.isArray(provider.models) ? provider.models : [];
353160
+ const providerConnectionChanged = !!previousProvider && (String(provider.base_url || provider.endpoint || "") !== previousProvider.base_url || String(provider.protocol || "") !== previousProvider.protocol);
353161
+ for (const rawModel of models) {
353162
+ if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
353163
+ const model = rawModel;
353164
+ const previousName = String(model._previous_name || model.previous_name || model.name || "");
353165
+ const previousModel = previousProvider?.models.find((candidate) => candidate.name === previousName);
353166
+ const edited = providerConnectionChanged || !!previousModel && modelConfigurationFingerprint(model) !== modelConfigurationFingerprint(previousModel);
353167
+ delete model._previous_name;
353168
+ delete model.previous_name;
353169
+ if (!edited) continue;
353170
+ model.validation = { level: "discovered", status: "degraded", checked_at: "", capabilities: {} };
353171
+ delete model.evaluation;
353172
+ model.speed_rating = "unknown";
353173
+ model.capability_rating = "unknown";
353174
+ }
353175
+ }
353176
+ }
353177
+ function modelConfigIsUnavailable(model) {
353178
+ const validationStatus = effectiveModelValidationStatus(model);
353179
+ if (model.validation?.level !== "discovered" && (validationStatus === "unavailable" || validationStatus === "auth_error" || validationStatus === "invalid_config")) return true;
353180
+ const evaluationStatus = validationStatus === "degraded" ? "degraded" : String(model.evaluation?.status || "").toLowerCase();
353181
+ return evaluationStatus === "unavailable" || evaluationStatus.startsWith("error");
353182
+ }
353032
353183
  function parseDeploymentSelectionValue2(value) {
353033
353184
  const marker = String(value || "").trim();
353034
353185
  if (!marker.startsWith("deployment:")) return null;
@@ -353045,9 +353196,9 @@ function parseDeploymentSelectionValue2(value) {
353045
353196
  function effectiveModelValidationStatus(model) {
353046
353197
  const raw = String(model.validation?.status || "").toLowerCase();
353047
353198
  if (raw === "auth_error") return raw;
353199
+ if (String(model.validation?.level || "").toLowerCase() === "discovered") return "degraded";
353048
353200
  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;
353049
353201
  if (textEvidence && raw === "unavailable") return "degraded";
353050
- if (!raw && String(model.validation?.level || "").toLowerCase() === "discovered") return "degraded";
353051
353202
  return ["verified", "degraded", "unavailable", "auth_error", "rate_limited", "invalid_config"].includes(raw) ? raw : "unavailable";
353052
353203
  }
353053
353204
  function routeToolIsReadOnly(name50, rawArgs) {
@@ -353164,6 +353315,184 @@ var ConversationKernel = class {
353164
353315
  followUp: queued?.followUp.slice() || []
353165
353316
  };
353166
353317
  }
353318
+ queueItems(target) {
353319
+ const runtime = this.findRuntime(target);
353320
+ if (!runtime) return [];
353321
+ return runtime.pendingNextTurn.flatMap((item) => {
353322
+ if (item.queueMode !== "followUp") return [];
353323
+ if (typeof item.message === "string") return [];
353324
+ const id = String(item.message.clientMessageId || "");
353325
+ if (!id) return [];
353326
+ return [{
353327
+ id,
353328
+ text: String(item.message.visibleUserInput || item.message.text || "").replace(/^\[Next queued while current turn is running\]\n/, ""),
353329
+ queueMode: item.queueMode,
353330
+ requestedMode: item.message.visibleMode,
353331
+ goalObjective: item.message.goalObjective,
353332
+ runId: item.message.runId,
353333
+ createdAt: String(item.message.createdAt || "")
353334
+ }];
353335
+ });
353336
+ }
353337
+ enqueueNext(target, input) {
353338
+ const runtime = this.findRuntime(target);
353339
+ if (!runtime?.activePromise || !runtime.runId) throw new Error("Target conversation is not running");
353340
+ const id = String(input.id || "").trim().slice(0, 200);
353341
+ const text = String(input.text || "").trim();
353342
+ if (!id || !text) throw new Error("Queue item id and text are required");
353343
+ const existing = this.queueItems(runtime.target).find((item) => item.id === id);
353344
+ if (existing) return existing;
353345
+ const prompt = `[Next queued while current turn is running]
353346
+ ${text}`;
353347
+ const createdAt = String(input.createdAt || (/* @__PURE__ */ new Date()).toISOString());
353348
+ runtime.pendingNextTurn.push({
353349
+ message: {
353350
+ text: prompt,
353351
+ visibleUserInput: text,
353352
+ visibleMode: String(input.requestedMode || "build"),
353353
+ goalObjective: String(input.goalObjective || "") || void 0,
353354
+ clientMessageId: id,
353355
+ runId: runtime.runId,
353356
+ createdAt
353357
+ },
353358
+ queueMode: "followUp"
353359
+ });
353360
+ runtime.runner.retainConversationContinuations([{
353361
+ content: prompt,
353362
+ queueMode: "followUp",
353363
+ clientMessageId: id,
353364
+ runId: runtime.runId,
353365
+ createdAt
353366
+ }]);
353367
+ this.trackQueuedMessage(runtime, prompt, "followUp");
353368
+ this.emitQueueUpdate(runtime);
353369
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353370
+ }
353371
+ updateQueueItem(target, idInput, textInput) {
353372
+ const runtime = this.findRuntime(target);
353373
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353374
+ const id = String(idInput || "").trim();
353375
+ const text = String(textInput || "").trim();
353376
+ if (!id || !text) throw new Error("Queue item id and text are required");
353377
+ const pending3 = runtime.pendingNextTurn.find((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353378
+ if (!pending3 || typeof pending3.message === "string") throw new Error("Queue item is no longer editable");
353379
+ const oldPrompt = pending3.message.text;
353380
+ pending3.message.text = `[Next queued while current turn is running]
353381
+ ${text}`;
353382
+ pending3.message.visibleUserInput = text;
353383
+ runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending3.queueMode, clientMessageId: id });
353384
+ runtime.runner.retainConversationContinuations([{
353385
+ content: pending3.message.text,
353386
+ queueMode: pending3.queueMode,
353387
+ clientMessageId: id,
353388
+ runId: pending3.message.runId
353389
+ }]);
353390
+ this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending3.message.text, pending3.queueMode);
353391
+ this.emitQueueUpdate(runtime);
353392
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353393
+ }
353394
+ deleteQueueItem(target, idInput) {
353395
+ const runtime = this.findRuntime(target);
353396
+ if (!runtime) return false;
353397
+ const id = String(idInput || "").trim();
353398
+ const index = runtime.pendingNextTurn.findIndex((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353399
+ if (index < 0) return false;
353400
+ const [removed] = runtime.pendingNextTurn.splice(index, 1);
353401
+ if (typeof removed.message !== "string") {
353402
+ runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
353403
+ this.consumeQueuedMessage(runtime, removed.message.text);
353404
+ }
353405
+ this.emitQueueUpdate(runtime);
353406
+ return true;
353407
+ }
353408
+ reorderQueueItems(target, orderedIdsInput) {
353409
+ const runtime = this.findRuntime(target);
353410
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353411
+ const currentItems = this.queueItems(runtime.target);
353412
+ const orderedIds = Array.isArray(orderedIdsInput) ? orderedIdsInput.map((id) => String(id || "").trim()) : [];
353413
+ const currentIds = currentItems.map((item) => item.id);
353414
+ const completeOrder = orderedIds.length === currentIds.length && new Set(orderedIds).size === orderedIds.length && orderedIds.every((id) => currentIds.includes(id));
353415
+ if (!completeOrder) throw new Error("A complete queue order with unique current item ids is required");
353416
+ const persistedIds = runtime.runner.conversationContinuations().filter((item) => item.queueMode === "followUp" && !!item.clientMessageId).map((item) => String(item.clientMessageId));
353417
+ if (persistedIds.length !== currentIds.length || new Set(persistedIds).size !== persistedIds.length || persistedIds.some((id) => !currentIds.includes(id))) {
353418
+ throw new Error("Persisted queue does not match the complete queue order");
353419
+ }
353420
+ const pendingById = new Map(runtime.pendingNextTurn.flatMap((item) => {
353421
+ if (item.queueMode !== "followUp" || typeof item.message === "string" || !item.message.clientMessageId) return [];
353422
+ return [[String(item.message.clientMessageId), item]];
353423
+ }));
353424
+ const reorderedPending = orderedIds.map((id) => pendingById.get(id));
353425
+ let nextIndex = 0;
353426
+ runtime.pendingNextTurn = runtime.pendingNextTurn.map((item) => {
353427
+ if (item.queueMode !== "followUp" || typeof item.message === "string" || !item.message.clientMessageId) return item;
353428
+ return reorderedPending[nextIndex++];
353429
+ });
353430
+ runtime.runner.reorderConversationContinuations(orderedIds);
353431
+ runtime.queued.followUp = orderedIds.map((id) => pendingById.get(id)).map((item) => typeof item.message === "string" ? item.message : item.message.text);
353432
+ this.emitQueueUpdate(runtime);
353433
+ return this.queueItems(runtime.target);
353434
+ }
353435
+ setQueuePaused(target, paused) {
353436
+ const runtime = this.findRuntime(target);
353437
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353438
+ runtime.queuePaused = paused;
353439
+ this.emitQueueUpdate(runtime);
353440
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
353441
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
353442
+ }
353443
+ return runtime.queuePaused;
353444
+ }
353445
+ queueAction(target, action, input = {}) {
353446
+ const runtime = this.findRuntime(target);
353447
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353448
+ let receipt;
353449
+ if (action === "enqueue") {
353450
+ this.enqueueNext(runtime.target, {
353451
+ id: String(input.id || ""),
353452
+ text: String(input.text || ""),
353453
+ requestedMode: input.requestedMode,
353454
+ goalObjective: input.goalObjective,
353455
+ createdAt: input.createdAt
353456
+ });
353457
+ } else if (action === "update") {
353458
+ this.updateQueueItem(runtime.target, String(input.id || ""), String(input.text || ""));
353459
+ } else if (action === "delete") {
353460
+ if (!this.deleteQueueItem(runtime.target, String(input.id || ""))) throw new Error("Queue item was not found");
353461
+ } else if (action === "reorder") {
353462
+ this.reorderQueueItems(runtime.target, input.orderedIds || []);
353463
+ } else if (action === "toggle_pause") {
353464
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
353465
+ } else if (action === "guide") {
353466
+ const item = this.queueItems(runtime.target).find((entry) => entry.id === String(input.id || ""));
353467
+ if (!item) throw new Error("Queue item was not found");
353468
+ receipt = this.enqueueGuide({
353469
+ clientMessageId: item.id,
353470
+ guideId: (0, import_crypto16.randomUUID)(),
353471
+ target: runtime.target,
353472
+ runId: runtime.runId,
353473
+ deliveryMode: "steer",
353474
+ text: item.goalObjective ? `Goal for the current Build:
353475
+ ${item.goalObjective}` : item.text,
353476
+ goalObjective: item.goalObjective,
353477
+ createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString()
353478
+ });
353479
+ if (receipt.status === "rejected") return {
353480
+ ok: false,
353481
+ queueItems: this.queueItems(runtime.target),
353482
+ queuePaused: runtime.queuePaused,
353483
+ queued: this.queued(runtime.target),
353484
+ receipt
353485
+ };
353486
+ this.deleteQueueItem(runtime.target, item.id);
353487
+ }
353488
+ return {
353489
+ ok: true,
353490
+ queueItems: this.queueItems(runtime.target),
353491
+ queuePaused: runtime.queuePaused,
353492
+ queued: this.queued(runtime.target),
353493
+ receipt
353494
+ };
353495
+ }
353167
353496
  events(target) {
353168
353497
  return this.findRuntime(target)?.events.slice() || [];
353169
353498
  }
@@ -353192,6 +353521,8 @@ var ConversationKernel = class {
353192
353521
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
353193
353522
  target: normalized,
353194
353523
  queued: this.queued(normalized),
353524
+ queueItems: this.queueItems(normalized),
353525
+ queuePaused: runtime?.queuePaused === true,
353195
353526
  workEvents: this.events(normalized),
353196
353527
  runtime: this.runtimeState(normalized),
353197
353528
  mode: runner.mode,
@@ -353627,7 +353958,7 @@ var ConversationKernel = class {
353627
353958
  if (runtime.stopRequestedRunId === runId) {
353628
353959
  stopped = true;
353629
353960
  this.settleCooperativeStop(runtime, runId);
353630
- } else if (runtime.pendingNextTurn.length > 0) {
353961
+ } else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353631
353962
  this.schedulePendingRuntimeContinuation(runtime, runId);
353632
353963
  }
353633
353964
  }
@@ -353662,7 +353993,7 @@ var ConversationKernel = class {
353662
353993
  return this.result(runtime, lastTokens);
353663
353994
  }
353664
353995
  for (; ; ) {
353665
- while (runtime.pendingNextTurn.length > 0) {
353996
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353666
353997
  if (runtime.stopRequestedRunId === runtime.runId) return this.result(runtime, lastTokens);
353667
353998
  const next = runtime.pendingNextTurn.shift();
353668
353999
  if (next.queueMode === "steer" && typeof next.message !== "string" && !!next.message.clientMessageId) {
@@ -353704,11 +354035,11 @@ ${batchText}`,
353704
354035
  this.mirrorHostIfTargetActive(runtime);
353705
354036
  return this.result(runtime, lastTokens);
353706
354037
  }
353707
- this.clearQueued(runtime);
354038
+ if (!runtime.pendingNextTurn.length) this.clearQueued(runtime);
353708
354039
  const completedRunId = runtime.runId;
353709
354040
  runtime.runner.finishConversationWorkRun(completedRunId, "completed");
353710
354041
  await new Promise((resolve16) => setImmediate(resolve16));
353711
- if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && runtime.pendingNextTurn.length > 0) {
354042
+ if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353712
354043
  runtime.guideAcceptanceClosedRunId = "";
353713
354044
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
353714
354045
  throw new Error(`Unable to resume deferred Guide work run ${completedRunId}`);
@@ -353799,6 +354130,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353799
354130
  events: [],
353800
354131
  pendingNextTurn: [],
353801
354132
  queued: { steering: [], followUp: [] },
354133
+ queuePaused: false,
353802
354134
  runId: "",
353803
354135
  generation: this.generations.get(target.runtimeKey) || 0,
353804
354136
  stopRequestedRunId: "",
@@ -353894,7 +354226,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353894
354226
  }
353895
354227
  setImmediate(() => {
353896
354228
  runtime.pendingContinuationRunId = void 0;
353897
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
354229
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused) return;
353898
354230
  const next = runtime.pendingNextTurn.shift();
353899
354231
  if (!next) return;
353900
354232
  const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
@@ -354045,7 +354377,36 @@ Review this persisted peer result and summarize or continue the parent task as n
354045
354377
  const text = typeof message === "string" ? message : message.text;
354046
354378
  const prompt = isSteer ? text : `[Next queued while current turn is running]
354047
354379
  ${text}`;
354048
- if (!isSteer) this.trackQueuedMessage(runtime, prompt, queueMode);
354380
+ if (!isSteer) {
354381
+ const structured = typeof message === "string" ? null : message;
354382
+ const clientMessageId = String(structured?.clientMessageId || (0, import_crypto16.randomUUID)());
354383
+ const queuedMessage = {
354384
+ ...structured || {},
354385
+ text: prompt,
354386
+ visibleUserInput: structured?.visibleUserInput || text,
354387
+ visibleMode: structured?.visibleMode || runtime.options.mode,
354388
+ clientMessageId,
354389
+ runId: structured?.runId || runtime.runId,
354390
+ createdAt: String(structured?.createdAt || (/* @__PURE__ */ new Date()).toISOString())
354391
+ };
354392
+ if (!runtime.pendingNextTurn.some((item) => typeof item.message !== "string" && item.message.clientMessageId === clientMessageId)) {
354393
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: "followUp" });
354394
+ }
354395
+ runtime.runner.retainConversationContinuations([{
354396
+ content: prompt,
354397
+ queueMode: "followUp",
354398
+ clientMessageId,
354399
+ runId: queuedMessage.runId,
354400
+ images: queuedMessage.images?.map((image) => ({ ...image })),
354401
+ attachments: queuedMessage.attachments?.map((attachment) => ({ ...attachment })),
354402
+ createdAt: queuedMessage.createdAt
354403
+ }]);
354404
+ this.trackQueuedMessage(runtime, prompt, "followUp");
354405
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId ? "Next message retained while stopping." : "Next message queued.");
354406
+ if (runtime.stopRequestedRunId === runtime.runId) runtime.forceStopArmedRunId = "";
354407
+ this.emitQueueUpdate(runtime);
354408
+ return;
354409
+ }
354049
354410
  if (runtime.stopRequestedRunId === runtime.runId) {
354050
354411
  runtime.forceStopArmedRunId = "";
354051
354412
  const structured = typeof message === "string" ? void 0 : message;
@@ -354091,6 +354452,12 @@ ${text}`;
354091
354452
  }
354092
354453
  if (changed) this.emitQueueUpdate(runtime);
354093
354454
  }
354455
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
354456
+ this.queueState(runtime);
354457
+ const list = queueMode === "steer" ? runtime.queued.steering : runtime.queued.followUp;
354458
+ const index = list.indexOf(oldMessage);
354459
+ if (index >= 0) list[index] = nextMessage;
354460
+ }
354094
354461
  emitQueueUpdate(runtime) {
354095
354462
  this.queueState(runtime);
354096
354463
  runtime.runner.emitWorkEvent({
@@ -354212,9 +354579,10 @@ ${text}`;
354212
354579
  const runId = runtime.runId;
354213
354580
  await Promise.resolve();
354214
354581
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId) return true;
354215
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) return false;
354582
+ const hasSteering = runtime.pendingNextTurn.some((item) => item.queueMode === "steer");
354583
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0) return false;
354216
354584
  runtime.guideAcceptanceClosedRunId = runId;
354217
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
354585
+ if (runtime.pendingNextTurn.some((item) => item.queueMode === "steer") || runtime.runner.subagents.readRootInbox().length > 0) {
354218
354586
  runtime.guideAcceptanceClosedRunId = "";
354219
354587
  return false;
354220
354588
  }
@@ -354367,6 +354735,9 @@ async function handle(request) {
354367
354735
  target
354368
354736
  });
354369
354737
  }
354738
+ if (request.method === "queue_action") {
354739
+ return kernel.queueAction(checkedTarget(request.params.target), request.params.action, request.params.input);
354740
+ }
354370
354741
  if (request.method === "checkpoint") return kernel.checkpoint(checkedTarget(request.params.target));
354371
354742
  if (request.method === "context_compress") {
354372
354743
  return kernel.compressContext(checkedTarget(request.params.target), request.params.options);