newmark-agent 0.5.3 → 0.5.4

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.
@@ -336933,6 +336933,10 @@ var ToolExecutor = class {
336933
336933
  async webSearch(query) {
336934
336934
  return this.wsearch(query);
336935
336935
  }
336936
+ /** OCR entry point for the runtime's final visual fallback. */
336937
+ async finalVisualFallbackOcr(dataUrl, signal) {
336938
+ return await this.localOcr.recognizeDataUrl(dataUrl, signal, "sparse-ui");
336939
+ }
336936
336940
  setHostProfile(profile) {
336937
336941
  this.hostProfile = { ...profile };
336938
336942
  }
@@ -340846,7 +340850,12 @@ async function runAgentKernel(agent) {
340846
340850
  try {
340847
340851
  const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
340848
340852
  const modelBeforeKernelRun = agent.model;
340849
- let lastTurn = await runWithCompressionResume([], false);
340853
+ const preflightVisualFallback = !agent.activeModelConfig()?.vision ? await agent.finalVisualFallback("vision input not supported by the selected model", processSignal) : null;
340854
+ let lastTurn = preflightVisualFallback ? { text: preflightVisualFallback, stopReason: "stop", errorMessage: "" } : await runWithCompressionResume([], false);
340855
+ if (preflightVisualFallback) {
340856
+ tokens.push({ type: "text", text: preflightVisualFallback });
340857
+ agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
340858
+ }
340850
340859
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
340851
340860
  tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
340852
340861
  }
@@ -340876,6 +340885,14 @@ async function runAgentKernel(agent) {
340876
340885
  await agent.waitForPlannedRouteRetry();
340877
340886
  lastTurn = await runWithCompressionResume([], false);
340878
340887
  }
340888
+ if (kernelTurnFailed(agent, lastTurn)) {
340889
+ const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
340890
+ if (visualFallback) {
340891
+ tokens.push({ type: "text", text: visualFallback });
340892
+ agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
340893
+ lastTurn = { ...lastTurn, text: visualFallback, errorMessage: "", stopReason: "stop" };
340894
+ }
340895
+ }
340879
340896
  if (kernelTurnFailed(agent, lastTurn)) {
340880
340897
  throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
340881
340898
  }
@@ -350820,6 +350837,60 @@ ${msg.content}
350820
350837
  };
350821
350838
  return results;
350822
350839
  }
350840
+ /**
350841
+ * Final visual safety net: OCR each submitted image and ask a text-only
350842
+ * request to conservatively repair the OCR. This is intentionally callable
350843
+ * only after a visual-input refusal and after same-provider vision routing
350844
+ * has been exhausted; the original image is never sent again.
350845
+ */
350846
+ async finalVisualFallback(errorText, signal) {
350847
+ if (!/(?:vision|image|multimodal|image_url|input_image).*(?:not supported|unsupported|拒绝|不支持|failed|failure|invalid)|(?:not supported|unsupported|拒绝|不支持).*(?:vision|image|multimodal|image_url|input_image)/i.test(String(errorText || ""))) return null;
350848
+ const current = this.activeModelConfig();
350849
+ if (!current) return null;
350850
+ const alternateVision = this.config.allModels().some(
350851
+ (model) => model.enabled !== false && model.provider_id === current.provider_id && model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url && !["unavailable", "auth_error", "invalid_config"].includes(String(model.evaluation?.status || model.validation?.status || "").toLowerCase())
350852
+ );
350853
+ if (alternateVision) return null;
350854
+ const latest = [...this.history].reverse().find((item) => item?.role === "user");
350855
+ const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
350856
+ const images = parts.map((part) => {
350857
+ const image = part.image_url;
350858
+ return image && typeof image === "object" ? String(image.url || "") : "";
350859
+ }).filter((value) => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
350860
+ if (!images.length) return null;
350861
+ const ocr = [];
350862
+ for (const [index, image] of images.entries()) {
350863
+ try {
350864
+ const result = await this.tools.finalVisualFallbackOcr(image, signal);
350865
+ if (result.ok && result.text.trim()) ocr.push({ index: index + 1, text: result.text.slice(0, 5e4), confidence: result.confidence });
350866
+ } catch {
350867
+ }
350868
+ }
350869
+ if (!ocr.length) return JSON.stringify({ ok: false, fallback: "mini_ocr_llm", error: "Local OCR returned no readable text; no visual content was fabricated." });
350870
+ const task = typeof latest?.content === "string" ? latest.content : "";
350871
+ const evidence = ocr.map((item) => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):
350872
+ ${item.text}`).join("\n\n");
350873
+ const prompt = `The provider rejected image input. Answer the user's task using only this approximate OCR evidence. Correct obvious character, spacing, and line-break errors only when supported by context. Preserve [uncertain] markers for ambiguity and never invent missing visual content.
350874
+ User task:
350875
+ ${task.slice(0, 12e3)}
350876
+ OCR evidence:
350877
+ ${evidence}`;
350878
+ let corrected = "";
350879
+ try {
350880
+ const provider = this.engineModel();
350881
+ if (provider) corrected = String(await provider.chat(this.activeModelName(), [{ role: "user", content: prompt }], "You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.", 0.05, 3e3, signal) || "").trim();
350882
+ } catch {
350883
+ }
350884
+ return JSON.stringify({
350885
+ ok: !!(corrected || ocr.length),
350886
+ fallback: "mini_ocr_llm",
350887
+ approximate: true,
350888
+ warning: "\u89C6\u89C9\u8F93\u5165\u88AB\u62D2\u7EDD\uFF1B\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u672C\u5730 OCR\uFF0C\u5E76\u7ECF\u6587\u672C\u6A21\u578B\u4FDD\u5B88\u6821\u6B63\uFF0C\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002",
350889
+ raw_ocr: ocr,
350890
+ corrected: corrected || ocr.map((item) => item.text).join("\n\n"),
350891
+ uncertainty: corrected ? "preserved" : "raw_ocr_only"
350892
+ }, null, 2);
350893
+ }
350823
350894
  engineModel() {
350824
350895
  if (this.forcedProvider) {
350825
350896
  const active = this.activeDeployment();
@@ -351202,8 +351273,10 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351202
351273
  }
351203
351274
  const selectedModel = this.activeModelConfig();
351204
351275
  if (images.length && !selectedModel?.vision) {
351205
- this.status = "idle";
351206
- return [{ type: "text", text: `[Vision unavailable] ${this.activeModelName() || this.model} has not passed image-input validation. Select a validated vision model before asking about attachments.` }];
351276
+ const hasSameProviderVision = selectedModel && this.config.allModels().some(
351277
+ (model) => model.enabled !== false && model.provider_id === selectedModel.provider_id && model.name !== selectedModel.name && !!model.vision
351278
+ );
351279
+ if (hasSameProviderVision) this.switchToFallbackModel("vision input not supported by the selected model");
351207
351280
  }
351208
351281
  const now2 = this.nowLabel();
351209
351282
  const visibleUserInput = inputEnvelope?.visibleUserInput === void 0 ? text : String(inputEnvelope.visibleUserInput || "");
@@ -990,6 +990,13 @@ export declare class Agent {
990
990
  isModelValidationRunning(): boolean;
991
991
  modelValidationStatus(): ModelValidationProgress;
992
992
  private runModelValidation;
993
+ /**
994
+ * Final visual safety net: OCR each submitted image and ask a text-only
995
+ * request to conservatively repair the OCR. This is intentionally callable
996
+ * only after a visual-input refusal and after same-provider vision routing
997
+ * has been exhausted; the original image is never sent again.
998
+ */
999
+ finalVisualFallback(errorText: string, signal?: AbortSignal): Promise<string | null>;
993
1000
  engineModel(): LLMProvider | null;
994
1001
  /**
995
1002
  * dev-0.4.3 模型原生思考强度档位映射表(模型名 → thinking_tier_map)。
@@ -6913,6 +6913,62 @@ class Agent {
6913
6913
  };
6914
6914
  return results;
6915
6915
  }
6916
+ /**
6917
+ * Final visual safety net: OCR each submitted image and ask a text-only
6918
+ * request to conservatively repair the OCR. This is intentionally callable
6919
+ * only after a visual-input refusal and after same-provider vision routing
6920
+ * has been exhausted; the original image is never sent again.
6921
+ */
6922
+ async finalVisualFallback(errorText, signal) {
6923
+ if (!/(?:vision|image|multimodal|image_url|input_image).*(?:not supported|unsupported|拒绝|不支持|failed|failure|invalid)|(?:not supported|unsupported|拒绝|不支持).*(?:vision|image|multimodal|image_url|input_image)/i.test(String(errorText || '')))
6924
+ return null;
6925
+ const current = this.activeModelConfig();
6926
+ if (!current)
6927
+ return null;
6928
+ const alternateVision = this.config.allModels().some(model => model.enabled !== false && model.provider_id === current.provider_id &&
6929
+ model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url &&
6930
+ !['unavailable', 'auth_error', 'invalid_config'].includes(String(model.evaluation?.status || model.validation?.status || '').toLowerCase()));
6931
+ if (alternateVision)
6932
+ return null;
6933
+ const latest = [...this.history].reverse().find(item => item?.role === 'user');
6934
+ const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
6935
+ const images = parts.map(part => {
6936
+ const image = part.image_url;
6937
+ return image && typeof image === 'object' ? String(image.url || '') : '';
6938
+ }).filter(value => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
6939
+ if (!images.length)
6940
+ return null;
6941
+ const ocr = [];
6942
+ for (const [index, image] of images.entries()) {
6943
+ try {
6944
+ const result = await this.tools.finalVisualFallbackOcr(image, signal);
6945
+ if (result.ok && result.text.trim())
6946
+ ocr.push({ index: index + 1, text: result.text.slice(0, 50_000), confidence: result.confidence });
6947
+ }
6948
+ catch { }
6949
+ }
6950
+ if (!ocr.length)
6951
+ return JSON.stringify({ ok: false, fallback: 'mini_ocr_llm', error: 'Local OCR returned no readable text; no visual content was fabricated.' });
6952
+ const task = typeof latest?.content === 'string' ? latest.content : '';
6953
+ const evidence = ocr.map(item => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):\n${item.text}`).join('\n\n');
6954
+ const prompt = `The provider rejected image input. Answer the user's task using only this approximate OCR evidence. Correct obvious character, spacing, and line-break errors only when supported by context. Preserve [uncertain] markers for ambiguity and never invent missing visual content.\nUser task:\n${task.slice(0, 12_000)}\nOCR evidence:\n${evidence}`;
6955
+ let corrected = '';
6956
+ try {
6957
+ const provider = this.engineModel();
6958
+ if (provider)
6959
+ corrected = String(await provider.chat(this.activeModelName(), [{ role: 'user', content: prompt }], 'You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.', 0.05, 3000, signal) || '').trim();
6960
+ }
6961
+ catch { }
6962
+ return JSON.stringify({
6963
+ ok: !!(corrected || ocr.length),
6964
+ fallback: 'mini_ocr_llm',
6965
+ approximate: true,
6966
+ warning: '视觉输入被拒绝;以下内容来自本地 OCR,并经文本模型保守校正,可能不完整。',
6967
+ raw_ocr: ocr,
6968
+ corrected: corrected || ocr.map(item => item.text).join('\n\n'),
6969
+ uncertainty: corrected ? 'preserved' : 'raw_ocr_only',
6970
+ }, null, 2);
6971
+ }
6916
6972
  engineModel() {
6917
6973
  if (this.forcedProvider) {
6918
6974
  const active = this.activeDeployment();
@@ -7353,8 +7409,13 @@ class Agent {
7353
7409
  }
7354
7410
  const selectedModel = this.activeModelConfig();
7355
7411
  if (images.length && !selectedModel?.vision) {
7356
- this.status = 'idle';
7357
- return [{ type: 'text', text: `[Vision unavailable] ${this.activeModelName() || this.model} has not passed image-input validation. Select a validated vision model before asking about attachments.` }];
7412
+ // Give the normal route planner first chance to select another
7413
+ // same-provider vision deployment. If none is available, the kernel
7414
+ // preflight invokes the final mini-OCR + text-only correction path.
7415
+ const hasSameProviderVision = selectedModel && this.config.allModels().some(model => model.enabled !== false && model.provider_id === selectedModel.provider_id &&
7416
+ model.name !== selectedModel.name && !!model.vision);
7417
+ if (hasSameProviderVision)
7418
+ this.switchToFallbackModel('vision input not supported by the selected model');
7358
7419
  }
7359
7420
  const now = this.nowLabel();
7360
7421
  const visibleUserInput = inputEnvelope?.visibleUserInput === undefined
@@ -429,7 +429,16 @@ async function runAgentKernel(agent) {
429
429
  try {
430
430
  const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
431
431
  const modelBeforeKernelRun = agent.model;
432
- let lastTurn = await runWithCompressionResume([], false);
432
+ const preflightVisualFallback = !agent.activeModelConfig()?.vision
433
+ ? await agent.finalVisualFallback('vision input not supported by the selected model', processSignal)
434
+ : null;
435
+ let lastTurn = preflightVisualFallback
436
+ ? { text: preflightVisualFallback, stopReason: 'stop', errorMessage: '' }
437
+ : await runWithCompressionResume([], false);
438
+ if (preflightVisualFallback) {
439
+ tokens.push({ type: 'text', text: preflightVisualFallback });
440
+ agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
441
+ }
433
442
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
434
443
  tokens.unshift({ type: 'text', text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
435
444
  }
@@ -460,6 +469,14 @@ async function runAgentKernel(agent) {
460
469
  await agent.waitForPlannedRouteRetry();
461
470
  lastTurn = await runWithCompressionResume([], false);
462
471
  }
472
+ if (kernelTurnFailed(agent, lastTurn)) {
473
+ const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
474
+ if (visualFallback) {
475
+ tokens.push({ type: 'text', text: visualFallback });
476
+ agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
477
+ lastTurn = { ...lastTurn, text: visualFallback, errorMessage: '', stopReason: 'stop' };
478
+ }
479
+ }
463
480
  if (kernelTurnFailed(agent, lastTurn)) {
464
481
  throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
465
482
  }
@@ -2,6 +2,7 @@ import { ConfigManager } from '../core/config';
2
2
  import { NewmarkToolDefinition, NewmarkToolResult } from '../core/compat';
3
3
  import { SshManager } from '../core/ssh';
4
4
  import { WorkspaceManager } from '../core/workspace';
5
+ import { LocalOcrResult } from '../core/localOcr';
5
6
  export interface ToolExecutionContext {
6
7
  mode?: string;
7
8
  workspacePath?: string;
@@ -30,6 +31,8 @@ export declare class ToolExecutor {
30
31
  private hostProfile;
31
32
  constructor(root: string, config: ConfigManager, ssh?: SshManager | undefined, workspace?: WorkspaceManager | undefined);
32
33
  webSearch(query: string): Promise<string>;
34
+ /** OCR entry point for the runtime's final visual fallback. */
35
+ finalVisualFallbackOcr(dataUrl: string, signal?: AbortSignal): Promise<LocalOcrResult>;
33
36
  setHostProfile(profile: ToolHostProfile): void;
34
37
  definitions(mode?: string): unknown[];
35
38
  canonicalDefinitions(mode?: string): NewmarkToolDefinition[];
@@ -234,6 +234,10 @@ class ToolExecutor {
234
234
  async webSearch(query) {
235
235
  return this.wsearch(query);
236
236
  }
237
+ /** OCR entry point for the runtime's final visual fallback. */
238
+ async finalVisualFallbackOcr(dataUrl, signal) {
239
+ return await this.localOcr.recognizeDataUrl(dataUrl, signal, 'sparse-ui');
240
+ }
237
241
  setHostProfile(profile) {
238
242
  this.hostProfile = { ...profile };
239
243
  }
@@ -336937,6 +336937,10 @@ var ToolExecutor = class {
336937
336937
  async webSearch(query) {
336938
336938
  return this.wsearch(query);
336939
336939
  }
336940
+ /** OCR entry point for the runtime's final visual fallback. */
336941
+ async finalVisualFallbackOcr(dataUrl, signal) {
336942
+ return await this.localOcr.recognizeDataUrl(dataUrl, signal, "sparse-ui");
336943
+ }
336940
336944
  setHostProfile(profile) {
336941
336945
  this.hostProfile = { ...profile };
336942
336946
  }
@@ -340850,7 +340854,12 @@ async function runAgentKernel(agent) {
340850
340854
  try {
340851
340855
  const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
340852
340856
  const modelBeforeKernelRun = agent.model;
340853
- let lastTurn = await runWithCompressionResume([], false);
340857
+ const preflightVisualFallback = !agent.activeModelConfig()?.vision ? await agent.finalVisualFallback("vision input not supported by the selected model", processSignal) : null;
340858
+ let lastTurn = preflightVisualFallback ? { text: preflightVisualFallback, stopReason: "stop", errorMessage: "" } : await runWithCompressionResume([], false);
340859
+ if (preflightVisualFallback) {
340860
+ tokens.push({ type: "text", text: preflightVisualFallback });
340861
+ agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
340862
+ }
340854
340863
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
340855
340864
  tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
340856
340865
  }
@@ -340880,6 +340889,14 @@ async function runAgentKernel(agent) {
340880
340889
  await agent.waitForPlannedRouteRetry();
340881
340890
  lastTurn = await runWithCompressionResume([], false);
340882
340891
  }
340892
+ if (kernelTurnFailed(agent, lastTurn)) {
340893
+ const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
340894
+ if (visualFallback) {
340895
+ tokens.push({ type: "text", text: visualFallback });
340896
+ agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
340897
+ lastTurn = { ...lastTurn, text: visualFallback, errorMessage: "", stopReason: "stop" };
340898
+ }
340899
+ }
340883
340900
  if (kernelTurnFailed(agent, lastTurn)) {
340884
340901
  throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
340885
340902
  }
@@ -350824,6 +350841,60 @@ ${msg.content}
350824
350841
  };
350825
350842
  return results;
350826
350843
  }
350844
+ /**
350845
+ * Final visual safety net: OCR each submitted image and ask a text-only
350846
+ * request to conservatively repair the OCR. This is intentionally callable
350847
+ * only after a visual-input refusal and after same-provider vision routing
350848
+ * has been exhausted; the original image is never sent again.
350849
+ */
350850
+ async finalVisualFallback(errorText, signal) {
350851
+ if (!/(?:vision|image|multimodal|image_url|input_image).*(?:not supported|unsupported|拒绝|不支持|failed|failure|invalid)|(?:not supported|unsupported|拒绝|不支持).*(?:vision|image|multimodal|image_url|input_image)/i.test(String(errorText || ""))) return null;
350852
+ const current = this.activeModelConfig();
350853
+ if (!current) return null;
350854
+ const alternateVision = this.config.allModels().some(
350855
+ (model) => model.enabled !== false && model.provider_id === current.provider_id && model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url && !["unavailable", "auth_error", "invalid_config"].includes(String(model.evaluation?.status || model.validation?.status || "").toLowerCase())
350856
+ );
350857
+ if (alternateVision) return null;
350858
+ const latest = [...this.history].reverse().find((item) => item?.role === "user");
350859
+ const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
350860
+ const images = parts.map((part) => {
350861
+ const image = part.image_url;
350862
+ return image && typeof image === "object" ? String(image.url || "") : "";
350863
+ }).filter((value) => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
350864
+ if (!images.length) return null;
350865
+ const ocr = [];
350866
+ for (const [index, image] of images.entries()) {
350867
+ try {
350868
+ const result = await this.tools.finalVisualFallbackOcr(image, signal);
350869
+ if (result.ok && result.text.trim()) ocr.push({ index: index + 1, text: result.text.slice(0, 5e4), confidence: result.confidence });
350870
+ } catch {
350871
+ }
350872
+ }
350873
+ if (!ocr.length) return JSON.stringify({ ok: false, fallback: "mini_ocr_llm", error: "Local OCR returned no readable text; no visual content was fabricated." });
350874
+ const task = typeof latest?.content === "string" ? latest.content : "";
350875
+ const evidence = ocr.map((item) => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):
350876
+ ${item.text}`).join("\n\n");
350877
+ const prompt = `The provider rejected image input. Answer the user's task using only this approximate OCR evidence. Correct obvious character, spacing, and line-break errors only when supported by context. Preserve [uncertain] markers for ambiguity and never invent missing visual content.
350878
+ User task:
350879
+ ${task.slice(0, 12e3)}
350880
+ OCR evidence:
350881
+ ${evidence}`;
350882
+ let corrected = "";
350883
+ try {
350884
+ const provider = this.engineModel();
350885
+ if (provider) corrected = String(await provider.chat(this.activeModelName(), [{ role: "user", content: prompt }], "You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.", 0.05, 3e3, signal) || "").trim();
350886
+ } catch {
350887
+ }
350888
+ return JSON.stringify({
350889
+ ok: !!(corrected || ocr.length),
350890
+ fallback: "mini_ocr_llm",
350891
+ approximate: true,
350892
+ warning: "\u89C6\u89C9\u8F93\u5165\u88AB\u62D2\u7EDD\uFF1B\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u672C\u5730 OCR\uFF0C\u5E76\u7ECF\u6587\u672C\u6A21\u578B\u4FDD\u5B88\u6821\u6B63\uFF0C\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002",
350893
+ raw_ocr: ocr,
350894
+ corrected: corrected || ocr.map((item) => item.text).join("\n\n"),
350895
+ uncertainty: corrected ? "preserved" : "raw_ocr_only"
350896
+ }, null, 2);
350897
+ }
350827
350898
  engineModel() {
350828
350899
  if (this.forcedProvider) {
350829
350900
  const active = this.activeDeployment();
@@ -351206,8 +351277,10 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351206
351277
  }
351207
351278
  const selectedModel = this.activeModelConfig();
351208
351279
  if (images.length && !selectedModel?.vision) {
351209
- this.status = "idle";
351210
- return [{ type: "text", text: `[Vision unavailable] ${this.activeModelName() || this.model} has not passed image-input validation. Select a validated vision model before asking about attachments.` }];
351280
+ const hasSameProviderVision = selectedModel && this.config.allModels().some(
351281
+ (model) => model.enabled !== false && model.provider_id === selectedModel.provider_id && model.name !== selectedModel.name && !!model.vision
351282
+ );
351283
+ if (hasSameProviderVision) this.switchToFallbackModel("vision input not supported by the selected model");
351211
351284
  }
351212
351285
  const now2 = this.nowLabel();
351213
351286
  const visibleUserInput = inputEnvelope?.visibleUserInput === void 0 ? text : String(inputEnvelope.visibleUserInput || "");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "newmark-agent",
3
3
  "productName": "Newmark Agent",
4
- "version": "0.5.3",
4
+ "version": "0.5.4",
5
5
  "description": "Newmark Agent — Portable AI coding agent with rich GUI and CLI (TypeScript)",
6
6
  "homepage": "https://github.com/positer/Newmark-Agent",
7
7
  "repository": {