omnius 1.0.232 → 1.0.233

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.
package/dist/index.js CHANGED
@@ -536919,37 +536919,35 @@ function findOrphans() {
536919
536919
  const isHot = parseFloat(cpu) > 10 ? " ⚠ HIGH CPU" : "";
536920
536920
  lines.push(` ${String(pid).padStart(7)} ${String(ppid).padStart(7)} ${String(cpu).padStart(6)} ${String(mem).padStart(5)} ${String(elapsed).padStart(9)} ${shortCmd}${isHot}`);
536921
536921
  }
536922
- lines.push(`
536923
- To kill a process: process_health(action='kill', pid=PID)`);
536924
- lines.push(`Or from the prompt: /destroy processes --global`);
536922
+ lines.push("\nThese are untracked diagnostics only. They are not killed automatically.");
536923
+ lines.push("To stop a process safely, it must have an active verified Omnius process lease.");
536924
+ lines.push("Use /destroy processes --global to sweep registered stale leases and report untracked matches.");
536925
536925
  return lines.join("\n");
536926
536926
  } catch {
536927
536927
  return "Could not scan for orphan processes.";
536928
536928
  }
536929
536929
  }
536930
- function killProcess2(pid) {
536931
- try {
536932
- try {
536933
- process.kill(-pid, "SIGTERM");
536934
- } catch {
536935
- }
536936
- process.kill(pid, "SIGTERM");
536937
- try {
536938
- execSync28(`sleep 1 && kill -0 ${pid} 2>/dev/null && kill -9 ${pid} 2>/dev/null`, {
536939
- timeout: 3e3,
536940
- stdio: "pipe"
536941
- });
536942
- } catch {
536943
- }
536944
- return `Killed PID ${pid} (SIGTERM → SIGKILL)`;
536945
- } catch (err) {
536946
- return `Failed to kill PID ${pid}: ${err instanceof Error ? err.message : String(err)}`;
536930
+ async function stopRegisteredLeaseByPid(pid) {
536931
+ const lease = listProcessLeases({ includeInactive: false }).find((candidate) => candidate.pid === pid && candidate.status === "active");
536932
+ if (!lease) {
536933
+ return `Refusing to kill PID ${pid}: no active Omnius process lease verifies ownership. Treat it as untracked_omnius_like diagnostics only and stop it manually if needed.`;
536934
+ }
536935
+ const action = await stopProcessLease(lease.leaseId, {
536936
+ reason: "process_health explicit stop"
536937
+ });
536938
+ if (action.action === "killed") {
536939
+ return `Stopped registered Omnius lease ${lease.leaseId} for PID ${pid}: ${action.reason}`;
536947
536940
  }
536941
+ if (action.action === "suspect_unverified") {
536942
+ return `Refusing to kill PID ${pid}: lease ${lease.leaseId} could not verify current PID identity.`;
536943
+ }
536944
+ return `PID ${pid} was not killed; lease ${lease.leaseId} action=${action.action}: ${action.reason}`;
536948
536945
  }
536949
536946
  var OMNIUS_PATTERNS, ProcessHealthTool;
536950
536947
  var init_process_health = __esm({
536951
536948
  "packages/execution/dist/tools/process-health.js"() {
536952
536949
  "use strict";
536950
+ init_process_lifecycle();
536953
536951
  OMNIUS_PATTERNS = [
536954
536952
  "omnius",
536955
536953
  "nexus-daemon",
@@ -536966,7 +536964,7 @@ var init_process_health = __esm({
536966
536964
  ].join("|");
536967
536965
  ProcessHealthTool = class {
536968
536966
  name = "process_health";
536969
- description = "Check system CPU/memory load and find orphaned processes. Actions: 'status' (CPU + memory + top processes), 'orphans' (find stale Omnius-related processes), 'kill' (kill a specific PID). Use this when the system feels slow or when a shell command seems stuck.";
536967
+ description = "Check system CPU/memory load and find orphaned processes. Actions: 'status' (CPU + memory + top processes), 'orphans' (find stale Omnius-related processes), 'kill' (stop a registered Omnius process lease by PID). Use this when the system feels slow or when a shell command seems stuck.";
536970
536968
  parameters = {
536971
536969
  type: "object",
536972
536970
  properties: {
@@ -536977,7 +536975,7 @@ var init_process_health = __esm({
536977
536975
  },
536978
536976
  pid: {
536979
536977
  type: "number",
536980
- description: "Process ID to kill (for 'kill' action)"
536978
+ description: "Process ID to stop, only when it has a verified active Omnius process lease"
536981
536979
  }
536982
536980
  },
536983
536981
  required: []
@@ -536998,7 +536996,7 @@ var init_process_health = __esm({
536998
536996
  if (pid === process.pid || pid === process.ppid) {
536999
536997
  return { success: false, output: "", error: "Cannot kill self or parent process", durationMs: performance.now() - start2 };
537000
536998
  }
537001
- return { success: true, output: killProcess2(pid), durationMs: performance.now() - start2 };
536999
+ return { success: true, output: await stopRegisteredLeaseByPid(pid), durationMs: performance.now() - start2 };
537002
537000
  }
537003
537001
  default:
537004
537002
  return { success: false, output: "", error: `Unknown action: ${action}`, durationMs: performance.now() - start2 };
@@ -621021,8 +621019,8 @@ sleep 1
621021
621019
  });
621022
621020
  items.push({
621023
621021
  key: "__kill__",
621024
- label: "Kill Omnius schedulers + active runs",
621025
- detail: "Stop scheduler/nexus processes and terminate active Omnius runs"
621022
+ label: "Stop registered schedulers + active runs",
621023
+ detail: "Disable scheduler sources and stop registry-owned Omnius runs"
621026
621024
  });
621027
621025
  const result = await tuiSelect({
621028
621026
  items,
@@ -621386,7 +621384,7 @@ sleep 1
621386
621384
  body: JSON.stringify({}),
621387
621385
  headers: { "Content-Type": "application/json" }
621388
621386
  });
621389
- renderInfo("Kill signal sent to Omnius scheduler processes.");
621387
+ renderInfo("Scheduler sources disabled and registered scheduler leases stopped where verified.");
621390
621388
  } catch (e2) {
621391
621389
  renderError(e2?.message || String(e2));
621392
621390
  }
@@ -645279,6 +645277,31 @@ function adaptTool5(tool, todoSessionId, progress) {
645279
645277
  }
645280
645278
  });
645281
645279
  }
645280
+ function normalizeTelegramImageAnalyzeDetail(value2) {
645281
+ const raw = String(value2 ?? "auto").trim().toLowerCase();
645282
+ return raw === "text" || raw === "visual" || raw === "full" ? raw : "auto";
645283
+ }
645284
+ function stripTelegramImagePayloadMarkers(value2) {
645285
+ return value2.replace(/\n?\[IMAGE_BASE64:[^\]]+\]/g, "\n[IMAGE_BASE64 omitted from display]");
645286
+ }
645287
+ function truncateTelegramStageOutput(value2, maxChars = 3500) {
645288
+ const clean5 = stripTelegramImagePayloadMarkers(value2).replace(/\n{3,}/g, "\n\n").trim();
645289
+ if (clean5.length <= maxChars) return clean5;
645290
+ return `${clean5.slice(0, maxChars).trimEnd()}
645291
+ ... (${clean5.length - maxChars} more chars omitted)`;
645292
+ }
645293
+ function telegramTextExtractionSignal(text) {
645294
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
645295
+ const chars = text.replace(/\s+/g, "").length;
645296
+ const longestLine = lines.reduce((max, line) => Math.max(max, line.length), 0);
645297
+ return {
645298
+ chars,
645299
+ lines: lines.length,
645300
+ longestLine,
645301
+ dense: chars >= 700 || lines.length >= 8 || longestLine >= 140,
645302
+ present: chars >= 8
645303
+ };
645304
+ }
645282
645305
  function telegramBotAccessSettingsFromApi(settings) {
645283
645306
  return {
645284
645307
  isAccessRestricted: Boolean(settings.is_access_restricted),
@@ -645414,7 +645437,7 @@ function renderTelegramSubAgentError(username, error) {
645414
645437
  const preview = error.length > 80 ? error.slice(0, 77) + "..." : error;
645415
645438
  renderTelegramCoalescedRow(`${c3.red("✘")} ${c3.bold(`@${username}`)}: ${c3.dim(preview)}`);
645416
645439
  }
645417
- var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTATING_GROUPS, DEFAULT_TELEGRAM_TOOL_GROUP_POLICY, TELEGRAM_TOOL_BUTTON_LABELS, TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, TELEGRAM_PUBLIC_VISION_STACK_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT, TELEGRAM_LINK_INTEGRITY_CONTRACT, TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT, TELEGRAM_INTERACTION_DECISION_MINIMAL_SCHEMA, TELEGRAM_INTERACTION_DECISION_REPAIR_SCHEMA, TELEGRAM_CHAT_REPLY_RESPONSE_FORMAT, TELEGRAM_SPACED_URL_RE, TELEGRAM_HTTP_URL_RE, TELEGRAM_STUCK_SELF_TALK_PREFIXES, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_ASSOCIATIVE_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_USER_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_ACTION_LIMIT, TELEGRAM_ASSOCIATIVE_RELATION_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_MEMORY_GENERIC_QUERY_TOKENS, TELEGRAM_SUB_AGENT_BOUNDED_OPTIONS, TELEGRAM_PUBLIC_FAST_OPTIONS, TELEGRAM_ADMIN_EVIDENCE_OPTIONS, TELEGRAM_SUB_AGENT_DEFAULT_LIMIT, TELEGRAM_SUB_AGENT_MAX_LIMIT, TELEGRAM_SUB_AGENT_BURST_CONTEXT_LIMIT, TELEGRAM_ADMIN_LIVE_PANEL_PAGES, TELEGRAM_ADMIN_LIVE_MUTATION_TOOLS, TELEGRAM_PUBLIC_HELP_COMMANDS2, TELEGRAM_REMINDER_SLASH_COMMANDS, TELEGRAM_REFLECTION_SLASH_COMMANDS, TELEGRAM_PUBLIC_BOT_COMMAND_NAMES, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TELEGRAM_CHANNEL_DMN_SWEEP_MS, TELEGRAM_CHANNEL_DMN_IDLE_AFTER_MS, TELEGRAM_CHANNEL_DMN_MIN_INTERVAL_MS, TELEGRAM_CHANNEL_DMN_MIN_MESSAGES, TELEGRAM_ALLOWED_UPDATES, TELEGRAM_DEFAULT_LONG_POLL_TIMEOUT_SECONDS, TELEGRAM_ROUTER_AUTO_MIN_PARAMETERS_B, TELEGRAM_PUBLIC_TOOL_QUOTAS, TelegramBridge;
645440
+ var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTATING_GROUPS, DEFAULT_TELEGRAM_TOOL_GROUP_POLICY, TELEGRAM_TOOL_BUTTON_LABELS, TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, TELEGRAM_PUBLIC_VISION_STACK_CONTRACT, TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT, TELEGRAM_LINK_INTEGRITY_CONTRACT, TELEGRAM_INTERACTION_DECISION_RESPONSE_FORMAT, TELEGRAM_INTERACTION_DECISION_MINIMAL_SCHEMA, TELEGRAM_INTERACTION_DECISION_REPAIR_SCHEMA, TELEGRAM_CHAT_REPLY_RESPONSE_FORMAT, TELEGRAM_SPACED_URL_RE, TELEGRAM_HTTP_URL_RE, TELEGRAM_STUCK_SELF_TALK_PREFIXES, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_ASSOCIATIVE_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_USER_FACT_LIMIT, TELEGRAM_ASSOCIATIVE_ACTION_LIMIT, TELEGRAM_ASSOCIATIVE_RELATION_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_MEMORY_GENERIC_QUERY_TOKENS, TELEGRAM_SUB_AGENT_BOUNDED_OPTIONS, TELEGRAM_PUBLIC_FAST_OPTIONS, TELEGRAM_ADMIN_EVIDENCE_OPTIONS, TELEGRAM_SUB_AGENT_DEFAULT_LIMIT, TELEGRAM_SUB_AGENT_MAX_LIMIT, TELEGRAM_SUB_AGENT_BURST_CONTEXT_LIMIT, TELEGRAM_ADMIN_LIVE_PANEL_PAGES, TELEGRAM_ADMIN_LIVE_MUTATION_TOOLS, TELEGRAM_PUBLIC_HELP_COMMANDS2, TELEGRAM_REMINDER_SLASH_COMMANDS, TELEGRAM_REFLECTION_SLASH_COMMANDS, TELEGRAM_PUBLIC_BOT_COMMAND_NAMES, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TELEGRAM_CHANNEL_DMN_SWEEP_MS, TELEGRAM_CHANNEL_DMN_IDLE_AFTER_MS, TELEGRAM_CHANNEL_DMN_MIN_INTERVAL_MS, TELEGRAM_CHANNEL_DMN_MIN_MESSAGES, TELEGRAM_ALLOWED_UPDATES, TELEGRAM_DEFAULT_LONG_POLL_TIMEOUT_SECONDS, TELEGRAM_ROUTER_AUTO_MIN_PARAMETERS_B, TELEGRAM_PUBLIC_TOOL_QUOTAS, TelegramBridge;
645418
645441
  var init_telegram_bridge = __esm({
645419
645442
  "packages/cli/src/tui/telegram-bridge.ts"() {
645420
645443
  "use strict";
@@ -645630,12 +645653,21 @@ Public Telegram vision and media stack
645630
645653
 
645631
645654
  Public Telegram runs have the full scoped media-analysis stack for media posted in this chat:
645632
645655
  - Use telegram_media_recent to find recent scoped media, then use path/media aliases 'reply' and 'latest' instead of exposing local paths to users.
645633
- - Use ocr_image_advanced for complex textual imagery: screenshots, dense documents, forms, receipts, scans, diagrams with labels, low-contrast photos, or uneven lighting.
645634
- - Use ocr for quick image text extraction, image_read for image metadata + OCR + multimodal image payload, and vision for captioning, visual QA, object detection, or pointing.
645656
+ - For image questions, prefer telegram_image_analyze first. It resolves omitted/reply/latest media, starts with low-fidelity image intake, uses basic OCR as the text extraction probe, escalates to advanced OCR when text is dense or under-extracted, and escalates to Moondream vision when visual QA/captioning is needed.
645657
+ - Use ocr for quick image text extraction, ocr_image_advanced when basic OCR shows dense or degraded text, image_read for image metadata + multimodal image payload, and vision for direct Moondream captioning, visual QA, object detection, or pointing.
645635
645658
  - Use pdf_to_text for embedded-text PDFs and ocr_pdf for scanned PDFs.
645636
645659
  - Use video_understand and transcribe_file for video/audio media posted in this chat.
645637
645660
  - Use identity_memory for explicit user-provided identity assertions, staged next-image names, and "who is this?" recall from scoped media. Do not guess real identities from images.
645638
645661
  - These tools are current-chat scoped. Never inspect arbitrary local files, reveal local paths, or claim access to media outside this Telegram chat scope.
645662
+ `.trim();
645663
+ TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT = `
645664
+ Evidence sufficiency contract
645665
+
645666
+ - Before making a factual claim, identify whether the answer is already supported by the supplied Telegram context, scoped memory, attached media extraction, or fresh tool/web evidence.
645667
+ - Use action/tools when the answer depends on current or recent external facts, volatile public events, named people or organizations in news-like claims, media extraction, source verification, or the user's wording asks you to find/check/look up something.
645668
+ - Quick-chat may answer only when the reply is conversational, based on supplied context, or based on stable common knowledge that is unlikely to have changed.
645669
+ - If a media tool call fails or lacks an argument, recover through telegram_image_analyze, telegram_media_recent, or the reply/latest aliases before telling the user you cannot inspect the media.
645670
+ - Do not fill evidence gaps with plausible memory. If evidence is missing and tools are unavailable, say what is missing instead of fabricating.
645639
645671
  `.trim();
645640
645672
  GROUP_REPLY_DISCRETION_PROMPT = `
645641
645673
  Reply discretion: you are in a group chat. The live router selected this turn
@@ -649477,6 +649509,31 @@ ${mediaContext}` : ""
649477
649509
  }
649478
649510
  return true;
649479
649511
  }
649512
+ telegramScopedMediaParameters(parameters, fields, description) {
649513
+ const base3 = parameters ?? {};
649514
+ const baseRecord = base3;
649515
+ const rawProperties = baseRecord["properties"];
649516
+ const properties = rawProperties && typeof rawProperties === "object" && !Array.isArray(rawProperties) ? { ...rawProperties } : {};
649517
+ for (const field of fields) {
649518
+ const property = properties[field];
649519
+ if (property && typeof property === "object" && !Array.isArray(property)) {
649520
+ const currentDescription = typeof property["description"] === "string" ? String(property["description"]) : "";
649521
+ properties[field] = {
649522
+ ...property,
649523
+ description: [currentDescription, description].filter(Boolean).join(" ")
649524
+ };
649525
+ }
649526
+ }
649527
+ const required = Array.isArray(baseRecord["required"]) ? baseRecord["required"].filter((item) => typeof item === "string" && !fields.includes(item)) : void 0;
649528
+ const rest = { ...baseRecord };
649529
+ delete rest["properties"];
649530
+ delete rest["required"];
649531
+ return {
649532
+ ...rest,
649533
+ properties,
649534
+ ...required && required.length > 0 ? { required } : {}
649535
+ };
649536
+ }
649480
649537
  resolveTelegramScopedMediaPath(rawValue, chatId, currentMsg, kind) {
649481
649538
  const raw = String(rawValue ?? "").trim();
649482
649539
  const repoRoot = this.repoRoot || ".";
@@ -650671,6 +650728,7 @@ ${lines.join("\n")}`);
650671
650728
  toolContext,
650672
650729
  authorityContext: [
650673
650730
  baseContract,
650731
+ TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT,
650674
650732
  TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT,
650675
650733
  TELEGRAM_PUBLIC_VISION_STACK_CONTRACT
650676
650734
  ].join("\n\n"),
@@ -651844,6 +651902,8 @@ ${stimulationProbe.context}`,
651844
651902
  `- chat: a short conversational answer can be produced without tools.`,
651845
651903
  `- action: tools, workspace context, media processing, web lookup, delegation, or a multi-step agent loop may be needed.`,
651846
651904
  `Route discipline: infer whether the current request can be completed as immediate conversation or needs an external capability. Do not route from keyword categories.`,
651905
+ `Evidence sufficiency: choose chat only when the answer is supported by supplied Telegram context, scoped memory, attached media context, or stable common knowledge. Choose action when answering would require web lookup, media extraction, current/recent public facts, named person/organization/news verification, or tool recovery.`,
651906
+ `Do not answer volatile external claims from model memory on the fast path. Route to action so web/media tools can gather evidence.`,
651847
651907
  ``,
651848
651908
  `Reply discretion: make a human-like attention decision from the full social context. Observe the message, relationship stream, reply graph, conversation momentum, prior bot involvement, speaker intent, and notification-like signals, then decide whether a visible reply would be natural.`,
651849
651909
  `No hard triggers: direct address, @mentions, name/identity references, private-chat delivery, replies, active threads, and stimulation score are evidence only. They may raise or lower salience, but none guarantees should_reply=true or should_reply=false.`,
@@ -652359,6 +652419,7 @@ Profile: ${profile}
652359
652419
  Tool context: ${toolContext}
652360
652420
  ${chatLabel}`,
652361
652421
  TELEGRAM_ACTION_RESPONSE_CONTRACT,
652422
+ TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT,
652362
652423
  TELEGRAM_LINK_INTEGRITY_CONTRACT
652363
652424
  ];
652364
652425
  sections.push(conversationStream);
@@ -652392,6 +652453,8 @@ ${TELEGRAM_PUBLIC_SOUL_PROFILE}
652392
652453
 
652393
652454
  ${TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT}
652394
652455
 
652456
+ ${TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT}
652457
+
652395
652458
  ${TELEGRAM_PUBLIC_VISION_STACK_CONTRACT}
652396
652459
 
652397
652460
  ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
@@ -652404,6 +652467,8 @@ ${TELEGRAM_PUBLIC_SOUL_PROFILE}
652404
652467
 
652405
652468
  ${TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT}
652406
652469
 
652470
+ ${TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT}
652471
+
652407
652472
  ${TELEGRAM_PUBLIC_VISION_STACK_CONTRACT}
652408
652473
 
652409
652474
  ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
@@ -654120,6 +654185,8 @@ ${TELEGRAM_PUBLIC_VISION_STACK_CONTRACT}`;
654120
654185
  role: "system",
654121
654186
  content: `${TELEGRAM_CHAT_MODE_PROMPT}
654122
654187
 
654188
+ ${TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT}
654189
+
654123
654190
  ${TELEGRAM_LINK_INTEGRITY_CONTRACT}
654124
654191
 
654125
654192
  ## Runtime Context
@@ -654467,6 +654534,8 @@ ${GROUP_REPLY_DISCRETION_PROMPT}` : "";
654467
654534
 
654468
654535
  ${TELEGRAM_ACTION_RESPONSE_CONTRACT}
654469
654536
 
654537
+ ${TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT}
654538
+
654470
654539
  ${TELEGRAM_LINK_INTEGRITY_CONTRACT}
654471
654540
 
654472
654541
  ${TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT}
@@ -654483,7 +654552,8 @@ ${currentTelegramPrompt}`;
654483
654552
  "You can remember facts about users and retrieve them later. Durable associative memory in the prompt includes participant profiles, relationships, scoped facts, and prior actions retained across days, sessions, and Omnius updates. You also have web_search and web_fetch to look up information.",
654484
654553
  TELEGRAM_LINK_INTEGRITY_CONTRACT,
654485
654554
  "If a user explicitly states a durable preference for reply cadence/order, call telegram_preference_set. Do not infer or classify reply-mode preferences from keywords, style, tone, or task type.",
654486
- "You have the full scoped Telegram media-analysis stack by default: telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. For complex textual imagery, screenshots, forms, scans, or dense labels, prefer ocr_image_advanced after resolving media with path='reply' or path='latest'.",
654555
+ TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT,
654556
+ "You have the full scoped Telegram media-analysis stack by default: telegram_image_analyze, telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. For image questions, prefer telegram_image_analyze first; it resolves omitted/reply/latest media, starts with low-fidelity image intake, uses basic OCR as the text extraction probe, escalates to advanced OCR when text is dense or under-extracted, and escalates to Moondream vision when visual QA/captioning is needed.",
654487
654557
  formatIdentityMemoryContext(chatLabel || "Telegram private chat"),
654488
654558
  reminderToolContract,
654489
654559
  "If the user asks you to create an image, audio file, video, 3D/CAD model, or document artifact, create it with the scoped creative tools. Freshly generated artifacts are recorded and automatically attached to this Telegram chat when the turn completes, so do not call telegram_send_file for those same artifacts unless the user asked for a specific caption, existing/unrecorded file, or non-default target.",
@@ -654959,6 +655029,7 @@ ${lines.join("\n")}`
654959
655029
  return {
654960
655030
  ...tool,
654961
655031
  description: "Read only images from this Telegram chat's media cache or creative workspace. Use path='reply' for the replied-to image or path='latest' for the most recent chat image.",
655032
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["path"], "In Telegram scope this is optional; omit it to use the replied-to image first, otherwise the latest chat image."),
654962
655033
  execute: async (args) => {
654963
655034
  const resolved = this.resolveTelegramScopedMediaPath(args["path"], chatId, currentMsg, "image");
654964
655035
  if (!resolved.ok) return { success: false, output: "", error: resolved.error };
@@ -654970,6 +655041,7 @@ ${lines.join("\n")}`
654970
655041
  return {
654971
655042
  ...tool,
654972
655043
  description: "Extract text only from images in this Telegram chat's media cache or creative workspace. Use path='reply' or path='latest' for chat media references.",
655044
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["path"], "In Telegram scope this is optional; omit it to use the replied-to image first, otherwise the latest chat image."),
654973
655045
  execute: async (args) => {
654974
655046
  const resolved = this.resolveTelegramScopedMediaPath(args["path"], chatId, currentMsg, "image");
654975
655047
  if (!resolved.ok) return { success: false, output: "", error: resolved.error };
@@ -654981,6 +655053,7 @@ ${lines.join("\n")}`
654981
655053
  return {
654982
655054
  ...tool,
654983
655055
  description: "Analyze only images from this Telegram chat's media cache or creative workspace. Use image='reply' for the replied-to image or image='latest' for the most recent chat image.",
655056
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["image"], "In Telegram scope this is optional; omit it to use the replied-to image first, otherwise the latest chat image."),
654984
655057
  execute: async (args) => {
654985
655058
  const resolved = this.resolveTelegramScopedMediaPath(args["image"], chatId, currentMsg, "image");
654986
655059
  if (!resolved.ok) return { success: false, output: "", error: resolved.error };
@@ -654992,6 +655065,7 @@ ${lines.join("\n")}`
654992
655065
  return {
654993
655066
  ...tool,
654994
655067
  description: "Advanced OCR only for images in this Telegram chat's media cache or creative workspace. Batch directory mode is disabled in public Telegram scope.",
655068
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["image"], "In Telegram scope this is optional; omit it to use the replied-to image first, otherwise the latest chat image."),
654995
655069
  execute: async (args) => {
654996
655070
  if (args["batch"] === true) return { success: false, output: "", error: "Batch directory OCR is not available in public Telegram scope." };
654997
655071
  const resolved = this.resolveTelegramScopedMediaPath(args["image"], chatId, currentMsg, "image");
@@ -655010,6 +655084,7 @@ ${lines.join("\n")}`
655010
655084
  return {
655011
655085
  ...tool,
655012
655086
  description: "Transcribe only audio/video files from this Telegram chat's media cache or creative workspace. Use path='reply' or path='latest' for chat media references.",
655087
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["path"], "In Telegram scope this is optional; omit it to use the replied-to audio/video first, otherwise the latest transcribable chat media."),
655013
655088
  execute: async (args) => {
655014
655089
  const resolved = this.resolveTelegramScopedMediaPath(args["path"], chatId, currentMsg, "transcribable");
655015
655090
  if (!resolved.ok) return { success: false, output: "", error: resolved.error };
@@ -655021,6 +655096,7 @@ ${lines.join("\n")}`
655021
655096
  return {
655022
655097
  ...tool,
655023
655098
  description: "Extract text only from PDFs in this Telegram chat's media cache or creative workspace. Use path='reply' or path='latest' for chat document references.",
655099
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["path"], "In Telegram scope this is optional; omit it to use the replied-to PDF first, otherwise the latest chat PDF."),
655024
655100
  execute: async (args) => {
655025
655101
  const resolved = this.resolveTelegramScopedMediaPath(args["path"], chatId, currentMsg, "pdf");
655026
655102
  if (!resolved.ok) return { success: false, output: "", error: resolved.error };
@@ -655032,6 +655108,7 @@ ${lines.join("\n")}`
655032
655108
  return {
655033
655109
  ...tool,
655034
655110
  description: "OCR only PDFs from this Telegram chat's media cache or creative workspace. Output, when requested, is forced into this chat's creative workspace.",
655111
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["input"], "In Telegram scope this is optional; omit it to use the replied-to PDF first, otherwise the latest chat PDF."),
655035
655112
  execute: async (args) => {
655036
655113
  const input = this.resolveTelegramScopedMediaPath(args["input"], chatId, currentMsg, "pdf");
655037
655114
  if (!input.ok) return { success: false, output: "", error: input.error };
@@ -655049,6 +655126,7 @@ ${lines.join("\n")}`
655049
655126
  return {
655050
655127
  ...tool,
655051
655128
  description: "Analyze only video files from this Telegram chat's media cache or creative workspace. URL download is disabled in public Telegram scope; use path='reply' or path='latest'.",
655129
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["path"], "In Telegram scope this is optional; omit it to use the replied-to video first, otherwise the latest chat video."),
655052
655130
  execute: async (args) => {
655053
655131
  if (args["url"]) return { success: false, output: "", error: "URL video analysis is not available in public Telegram scope. Use a video posted in this chat." };
655054
655132
  const resolved = this.resolveTelegramScopedMediaPath(args["path"], chatId, currentMsg, "video");
@@ -655061,6 +655139,7 @@ ${lines.join("\n")}`
655061
655139
  return {
655062
655140
  ...tool,
655063
655141
  description: "Analyze only audio files from this Telegram chat's media cache or creative workspace. Microphone/listen mode is disabled in public Telegram scope.",
655142
+ parameters: this.telegramScopedMediaParameters(tool.parameters, ["file", "path"], "In Telegram scope file/path is optional; omit it to use the replied-to audio first, otherwise the latest chat audio."),
655064
655143
  execute: async (args) => {
655065
655144
  if (String(args["action"] || "").toLowerCase() === "listen") {
655066
655145
  return { success: false, output: "", error: "Continuous microphone listening is not available in Telegram public scope." };
@@ -655139,6 +655218,8 @@ ${lines.join("\n")}`
655139
655218
 
655140
655219
  ${TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT}
655141
655220
 
655221
+ ${TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT}
655222
+
655142
655223
  ${TELEGRAM_PUBLIC_VISION_STACK_CONTRACT}
655143
655224
 
655144
655225
  ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}
@@ -655246,6 +655327,7 @@ Scoped workspace: ${scopedRoot}`,
655246
655327
  new WebSearchTool(),
655247
655328
  new WebCrawlTool(repoRoot),
655248
655329
  // Vision/OCR tools
655330
+ this.buildTelegramImageAnalyzeTool(repoRoot, chatId, msg),
655249
655331
  new ImageReadTool(repoRoot),
655250
655332
  new OCRTool(repoRoot),
655251
655333
  new VisionTool(repoRoot),
@@ -655304,6 +655386,7 @@ Scoped workspace: ${scopedRoot}`,
655304
655386
  new StructuredReadTool(repoRoot),
655305
655387
  new StructuredFileTool(repoRoot),
655306
655388
  new CodeSandboxTool(repoRoot),
655389
+ this.buildTelegramImageAnalyzeTool(repoRoot, chatId, msg),
655307
655390
  new ImageReadTool(repoRoot),
655308
655391
  new ScreenshotTool(repoRoot),
655309
655392
  new OCRTool(repoRoot),
@@ -655424,7 +655507,7 @@ Scoped workspace: ${scopedRoot}`,
655424
655507
  }
655425
655508
  telegramPublicQuotaKind(toolName) {
655426
655509
  if (toolName === "web_fetch") return "web";
655427
- if (/^(image_read|ocr|ocr_image_advanced|ocr_pdf|pdf_to_text|vision|transcribe_file|video_understand|audio_analyze)$/.test(toolName)) return "media";
655510
+ if (/^(telegram_image_analyze|image_read|ocr|ocr_image_advanced|ocr_pdf|pdf_to_text|vision|transcribe_file|video_understand|audio_analyze)$/.test(toolName)) return "media";
655428
655511
  if (toolName === "generate_video") return "video-generation";
655429
655512
  if (/^(generate_image|generate_audio|generate_model|generate_tts|create_audio_file)$/.test(toolName)) return "generation";
655430
655513
  if (toolName === "telegram_send_file") return "upload";
@@ -656728,11 +656811,144 @@ Scoped workspace: ${scopedRoot}`,
656728
656811
  }
656729
656812
  };
656730
656813
  }
656814
+ buildTelegramImageAnalyzeTool(repoRoot, chatId, currentMsg) {
656815
+ const bridge = this;
656816
+ return {
656817
+ name: "telegram_image_analyze",
656818
+ description: [
656819
+ "Analyze an image from the current Telegram chat using a staged evidence ladder.",
656820
+ "Omit image, or use image='reply', image='latest', message_id:<id>, or a listed basename.",
656821
+ "The tool starts with low-fidelity image intake, uses basic OCR as the text extraction probe, escalates to advanced OCR when text is dense or under-extracted, and escalates to Moondream vision for visual QA/captioning."
656822
+ ].join(" "),
656823
+ parameters: {
656824
+ type: "object",
656825
+ properties: {
656826
+ image: {
656827
+ type: "string",
656828
+ description: "Optional Telegram media reference. Omit for replied-to image first, otherwise latest image in this chat."
656829
+ },
656830
+ question: {
656831
+ type: "string",
656832
+ description: "Optional visual question to answer about the image."
656833
+ },
656834
+ detail: {
656835
+ type: "string",
656836
+ enum: ["auto", "text", "visual", "full"],
656837
+ description: "auto chooses stages from observed extraction signal; text emphasizes OCR; visual emphasizes Moondream; full runs both."
656838
+ }
656839
+ }
656840
+ },
656841
+ async execute(args) {
656842
+ const start2 = performance.now();
656843
+ const resolved = bridge.resolveTelegramScopedMediaPath(args["image"], chatId, currentMsg, "image");
656844
+ if (!resolved.ok) {
656845
+ return { success: false, output: "", error: resolved.error, durationMs: performance.now() - start2 };
656846
+ }
656847
+ const detail = normalizeTelegramImageAnalyzeDetail(args["detail"]);
656848
+ const question = String(args["question"] ?? args["prompt"] ?? currentMsg?.text ?? "").trim();
656849
+ const stageLines = [
656850
+ `Telegram image analysis: media=${basename35(resolved.path)} detail=${detail}`,
656851
+ "Stage ladder: low_fidelity_image_read -> basic OCR probe -> advanced OCR when text density/under-extraction warrants it -> Moondream vision when visual QA/captioning is warranted."
656852
+ ];
656853
+ const llmParts = [];
656854
+ const runStage = async (name10, reason, fn) => {
656855
+ const result = await fn();
656856
+ const body = result.success ? truncateTelegramStageOutput(result.output || result.llmContent || "") : `ERROR: ${result.error || result.output || "stage failed"}`;
656857
+ stageLines.push(`
656858
+ ## ${name10}
656859
+ Reason: ${reason}
656860
+ ${body}`);
656861
+ llmParts.push(`## ${name10}
656862
+ Reason: ${reason}
656863
+ ${result.llmContent ?? result.output ?? ""}${result.success ? "" : `
656864
+ ERROR: ${result.error || "stage failed"}`}`);
656865
+ return result;
656866
+ };
656867
+ const imageRead = await runStage(
656868
+ "low_fidelity_image_read",
656869
+ "Always start with scoped image metadata and a multimodal image payload for the model.",
656870
+ () => new ImageReadTool(repoRoot).execute({
656871
+ path: resolved.path,
656872
+ ocr: false,
656873
+ max_size_kb: typeof args["max_size_kb"] === "number" ? args["max_size_kb"] : 10240
656874
+ })
656875
+ );
656876
+ const textRequested = detail === "text" || detail === "full";
656877
+ const visualRequested = detail === "visual" || detail === "full";
656878
+ const shouldRunTextProbe = detail !== "visual";
656879
+ let textStage = null;
656880
+ let textSignal = telegramTextExtractionSignal("");
656881
+ if (shouldRunTextProbe) {
656882
+ textStage = await runStage(
656883
+ "ocr",
656884
+ "After low-fidelity intake, basic OCR measures whether text extraction is sufficient before any heavier escalation.",
656885
+ () => new OCRTool(repoRoot).execute({
656886
+ path: resolved.path,
656887
+ language: typeof args["language"] === "string" ? args["language"] : "eng"
656888
+ })
656889
+ );
656890
+ textSignal = telegramTextExtractionSignal(textStage.output ?? "");
656891
+ stageLines.push(
656892
+ `
656893
+ Text extraction signal: chars=${textSignal.chars}, lines=${textSignal.lines}, longest_line=${textSignal.longestLine}, dense=${textSignal.dense ? "yes" : "no"}.`
656894
+ );
656895
+ llmParts.push(`Text extraction signal: ${JSON.stringify(textSignal)}`);
656896
+ const shouldUseAdvancedOcr = detail === "full" || textSignal.dense || textRequested && !textSignal.present;
656897
+ if (shouldUseAdvancedOcr) {
656898
+ textStage = await runStage(
656899
+ "ocr_image_advanced",
656900
+ textSignal.dense ? "Basic OCR observed dense text, so multi-pass OCR is warranted." : "The request emphasized text extraction and basic intake did not provide enough text.",
656901
+ () => new OcrImageAdvancedTool(repoRoot).execute({
656902
+ image: resolved.path,
656903
+ language: typeof args["language"] === "string" ? args["language"] : "eng"
656904
+ })
656905
+ );
656906
+ const advancedSignal = telegramTextExtractionSignal(textStage.output ?? "");
656907
+ stageLines.push(
656908
+ `
656909
+ Advanced text extraction signal: chars=${advancedSignal.chars}, lines=${advancedSignal.lines}, longest_line=${advancedSignal.longestLine}, dense=${advancedSignal.dense ? "yes" : "no"}.`
656910
+ );
656911
+ llmParts.push(`Advanced text extraction signal: ${JSON.stringify(advancedSignal)}`);
656912
+ if (advancedSignal.present || !textSignal.present) textSignal = advancedSignal;
656913
+ }
656914
+ } else {
656915
+ stageLines.push("\n## OCR skipped\nReason: Visual detail was explicitly requested and text extraction was not needed.");
656916
+ llmParts.push("## OCR skipped\nReason: Visual detail was explicitly requested and text extraction was not needed.");
656917
+ }
656918
+ const shouldRunVision = visualRequested || Boolean(question && detail !== "text") || !textSignal.present && detail !== "text";
656919
+ let visionStage = null;
656920
+ if (shouldRunVision) {
656921
+ visionStage = await runStage(
656922
+ "vision_moondream",
656923
+ question ? "A visual question is present, so Moondream visual QA is warranted after lower-cost intake." : "Text extraction did not provide enough content or visual detail was requested, so Moondream captioning is warranted.",
656924
+ () => new VisionTool(repoRoot).execute({
656925
+ image: resolved.path,
656926
+ action: question ? "query" : "caption",
656927
+ ...question ? { prompt: question } : {},
656928
+ length: "normal"
656929
+ })
656930
+ );
656931
+ } else {
656932
+ stageLines.push("\n## vision_moondream skipped\nReason: OCR/text extraction produced sufficient evidence for the requested detail.");
656933
+ llmParts.push("## vision_moondream skipped\nReason: OCR/text extraction produced sufficient evidence for the requested detail.");
656934
+ }
656935
+ const anySuccess = imageRead.success || Boolean(textStage?.success) || Boolean(visionStage?.success);
656936
+ const output = stageLines.join("\n");
656937
+ return {
656938
+ success: anySuccess,
656939
+ output,
656940
+ llmContent: llmParts.join("\n\n"),
656941
+ error: anySuccess ? void 0 : "Telegram image analysis could not extract usable image evidence.",
656942
+ durationMs: performance.now() - start2
656943
+ };
656944
+ }
656945
+ };
656946
+ }
656731
656947
  buildTelegramMediaRecentTool(chatId, currentMsg) {
656732
656948
  const bridge = this;
656733
656949
  return {
656734
656950
  name: "telegram_media_recent",
656735
- description: "List recent media files available in this Telegram chat scope, including safe aliases for image_read, ocr, ocr_image_advanced, vision, identity_memory, transcribe_file, pdf_to_text, video_understand, and audio_analyze.",
656951
+ description: "List recent media files available in this Telegram chat scope, including safe aliases for telegram_image_analyze, image_read, ocr, ocr_image_advanced, vision, identity_memory, transcribe_file, pdf_to_text, video_understand, and audio_analyze.",
656736
656952
  parameters: {
656737
656953
  type: "object",
656738
656954
  properties: {
@@ -657140,7 +657356,7 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
657140
657356
  description = `[${sourceLabel}image received: path_alias=${mediaAlias}${safeCaption}
657141
657357
  ${visionContext}]`;
657142
657358
  } else {
657143
- description = `[${sourceLabel}image received: path_alias=${mediaAlias}${safeCaption}. Use path='${source === "reply" ? "reply" : "latest"}' or path='${mediaAlias}' with image_read, ocr, ocr_image_advanced, vision, or identity_memory.]`;
657359
+ description = `[${sourceLabel}image received: path_alias=${mediaAlias}${safeCaption}. Use image='${source === "reply" ? "reply" : "latest"}' or image='${mediaAlias}' with telegram_image_analyze, or use path/image aliases with image_read, ocr, ocr_image_advanced, vision, or identity_memory.]`;
657144
657360
  }
657145
657361
  const ingestPayload = this.telegramMemoryIngestPayload(msg, media, localPath, source, cacheEntry.extractedContent);
657146
657362
  let visualIdentityContext = "";
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.232",
3
+ "version": "1.0.233",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.232",
9
+ "version": "1.0.233",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
@@ -6690,9 +6690,9 @@
6690
6690
  }
6691
6691
  },
6692
6692
  "node_modules/streamx": {
6693
- "version": "2.26.0",
6694
- "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz",
6695
- "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==",
6693
+ "version": "2.27.0",
6694
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz",
6695
+ "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==",
6696
6696
  "license": "MIT",
6697
6697
  "optional": true,
6698
6698
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.232",
3
+ "version": "1.0.233",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",