omnius 1.0.368 → 1.0.370

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
@@ -9629,6 +9629,14 @@ var init_vision = __esm({
9629
9629
  type: "string",
9630
9630
  description: "Path to the image file (PNG, JPG, GIF, WebP, BMP, TIFF)"
9631
9631
  },
9632
+ path: {
9633
+ type: "string",
9634
+ description: "Alias for image path"
9635
+ },
9636
+ file: {
9637
+ type: "string",
9638
+ description: "Alias for image path"
9639
+ },
9632
9640
  action: {
9633
9641
  type: "string",
9634
9642
  enum: ["caption", "query", "detect", "point"],
@@ -9664,13 +9672,13 @@ var init_vision = __esm({
9664
9672
  }
9665
9673
  async execute(args) {
9666
9674
  const start2 = performance.now();
9667
- const rawPath = args["image"];
9675
+ const rawPath = args["image"] ?? args["path"] ?? args["file"];
9668
9676
  const action = args["action"] ?? "caption";
9669
9677
  const prompt = args["prompt"];
9670
9678
  const length4 = args["length"] ?? "normal";
9671
9679
  const preferredModel = normalizeVisionModelName(args["model"]);
9672
9680
  if (!rawPath) {
9673
- return { success: false, output: "", error: "image path is required", durationMs: 0 };
9681
+ return { success: false, output: "", error: "image path is required (accepted keys: image, path, file)", durationMs: 0 };
9674
9682
  }
9675
9683
  if ((action === "query" || action === "detect" || action === "point") && !prompt) {
9676
9684
  return {
@@ -35488,11 +35496,13 @@ var init_image = __esm({
35488
35496
  ImageReadTool = class {
35489
35497
  workingDir;
35490
35498
  name = "image_read";
35491
- description = "Read an image file and return its base64 encoding, dimensions, and OCR text extraction. The base64 data can be used for multimodal model input. Supports: PNG, JPG, GIF, WebP, BMP, TIFF, SVG.";
35499
+ description = "Read an image file and return its base64 encoding, dimensions, and OCR text extraction. The base64 data can be used for multimodal model input. For semantic visual understanding, object/person identification, or captions, prefer vision first (Moondream) and use image_read for metadata/OCR/raw image ingress. Supports: PNG, JPG, GIF, WebP, BMP, TIFF, SVG.";
35492
35500
  parameters = {
35493
35501
  type: "object",
35494
35502
  properties: {
35495
35503
  path: { type: "string", description: "Path to the image file" },
35504
+ file: { type: "string", description: "Alias for path" },
35505
+ image: { type: "string", description: "Alias for path" },
35496
35506
  ocr: { type: "boolean", description: "Extract text via OCR (default: true if tesseract available)" },
35497
35507
  max_size_kb: { type: "number", description: "Maximum file size in KB to read (default: 10240 = 10MB)" }
35498
35508
  },
@@ -35503,11 +35513,11 @@ var init_image = __esm({
35503
35513
  }
35504
35514
  async execute(args) {
35505
35515
  const start2 = Date.now();
35506
- const rawPath = String(args["path"] ?? "");
35516
+ const rawPath = String(args["path"] ?? args["file"] ?? args["image"] ?? "");
35507
35517
  const doOcr = args["ocr"] !== false;
35508
35518
  const maxSizeKb = typeof args["max_size_kb"] === "number" ? args["max_size_kb"] : 10240;
35509
35519
  if (!rawPath) {
35510
- return { success: false, output: "", error: "path is required", durationMs: 0 };
35520
+ return { success: false, output: "", error: "path is required (accepted keys: path, file, image)", durationMs: 0 };
35511
35521
  }
35512
35522
  const fullPath = resolve19(this.workingDir, rawPath);
35513
35523
  if (!existsSync31(fullPath)) {
@@ -269267,8 +269277,8 @@ var require_pattern = __commonJS({
269267
269277
  }
269268
269278
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
269269
269279
  function isAffectDepthOfReadingPattern(pattern) {
269270
- const basename39 = path12.basename(pattern);
269271
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename39);
269280
+ const basename40 = path12.basename(pattern);
269281
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename40);
269272
269282
  }
269273
269283
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
269274
269284
  function expandPatternsWithBraceExpansion(patterns) {
@@ -273144,7 +273154,7 @@ print("${sentinel}")
273144
273154
  if (!this.proc || this.proc.killed) {
273145
273155
  return { success: false, path: "" };
273146
273156
  }
273147
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = await import("node:fs");
273157
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = await import("node:fs");
273148
273158
  const sessionDir2 = join43(this.cwd, ".omnius", "rlm");
273149
273159
  mkdirSync106(sessionDir2, { recursive: true });
273150
273160
  const sessionPath2 = join43(sessionDir2, "session.json");
@@ -273170,7 +273180,7 @@ print("__SESSION__" + json.dumps(_session) + "__SESSION__")
273170
273180
  trajectoryCount: this.trajectory.length,
273171
273181
  subCallCount: this.subCallCount
273172
273182
  };
273173
- writeFileSync91(sessionPath2, JSON.stringify(sessionData, null, 2), "utf8");
273183
+ writeFileSync90(sessionPath2, JSON.stringify(sessionData, null, 2), "utf8");
273174
273184
  return { success: true, path: sessionPath2 };
273175
273185
  }
273176
273186
  } catch {
@@ -273789,7 +273799,7 @@ ${issues.map((i2) => ` - ${i2}`).join("\n")}` : " No issues found."),
273789
273799
  /** Update memory scores based on task outcome. Called after task completion.
273790
273800
  * Memories used in successful tasks get boosted. Memories present during failures get decayed. */
273791
273801
  updateFromOutcomeSync(surfacedMemoryText, succeeded) {
273792
- const { readFileSync: readFileSync133, writeFileSync: writeFileSync91, existsSync: existsSync164, mkdirSync: mkdirSync106 } = __require("node:fs");
273802
+ const { readFileSync: readFileSync133, writeFileSync: writeFileSync90, existsSync: existsSync164, mkdirSync: mkdirSync106 } = __require("node:fs");
273793
273803
  const metaDir = join44(this.cwd, ".omnius", "memory", "metabolism");
273794
273804
  const storeFile = join44(metaDir, "store.json");
273795
273805
  if (!existsSync164(storeFile))
@@ -273821,7 +273831,7 @@ ${issues.map((i2) => ` - ${i2}`).join("\n")}` : " No issues found."),
273821
273831
  }
273822
273832
  if (updated) {
273823
273833
  mkdirSync106(metaDir, { recursive: true });
273824
- writeFileSync91(storeFile, JSON.stringify(store2, null, 2));
273834
+ writeFileSync90(storeFile, JSON.stringify(store2, null, 2));
273825
273835
  }
273826
273836
  }
273827
273837
  // ── Storage ──────────────────────────────────────────────────────────
@@ -274267,7 +274277,7 @@ Recommendation: Strategy ${scored[0].index + 1} scores highest.`;
274267
274277
  }
274268
274278
  /** Archive a strategy variant synchronously (for task completion path) */
274269
274279
  archiveVariantSync(strategy, outcome, tags = []) {
274270
- const { readFileSync: readFileSync133, writeFileSync: writeFileSync91, existsSync: existsSync164, mkdirSync: mkdirSync106 } = __require("node:fs");
274280
+ const { readFileSync: readFileSync133, writeFileSync: writeFileSync90, existsSync: existsSync164, mkdirSync: mkdirSync106 } = __require("node:fs");
274271
274281
  const dir = join46(this.cwd, ".omnius", "arche");
274272
274282
  const archiveFile = join46(dir, "variants.json");
274273
274283
  let variants = [];
@@ -274289,7 +274299,7 @@ Recommendation: Strategy ${scored[0].index + 1} scores highest.`;
274289
274299
  if (variants.length > 50)
274290
274300
  variants = variants.slice(-50);
274291
274301
  mkdirSync106(dir, { recursive: true });
274292
- writeFileSync91(archiveFile, JSON.stringify(variants, null, 2));
274302
+ writeFileSync90(archiveFile, JSON.stringify(variants, null, 2));
274293
274303
  }
274294
274304
  async saveArchive(variants) {
274295
274305
  const dir = join46(this.cwd, ".omnius", "arche");
@@ -295142,7 +295152,13 @@ var init_todo_write = __esm({
295142
295152
  - completed: fully done (tests pass, code works, goal met)
295143
295153
  - blocked: stuck on a dependency (include blocker text)
295144
295154
 
295145
- Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark completed if tests are failing or implementation is partial. The user watches this list in the chat UI in real time. Canonical call shape: todo_write({"todos":[{"content":"Inspect files","status":"in_progress"},{"content":"Make changes","status":"pending"},{"content":"Verify results","status":"pending"}]})`;
295155
+ ## Nested decomposition
295156
+ - Use stable ids plus parentId to create subtasks under a parent objective.
295157
+ - Work on leaf subtasks; a parent is completed only after every child is completed with evidence.
295158
+ - When evidence changes the plan, rewrite the tree: add new child todos, block invalidated children with root cause, and keep the parent in_progress/blocked until the child set is truthful.
295159
+ - If a tool fails for a child, do not mark that child completed. Reconfigure it: re-read/observe current state, choose a different target/tool, or leave it blocked.
295160
+
295161
+ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark completed if tests are failing or implementation is partial. The user watches this list in the chat UI in real time. Canonical call shape: todo_write({"todos":[{"id":"p1","content":"Implement feature","status":"in_progress"},{"id":"c1","parentId":"p1","content":"Inspect files","status":"in_progress"},{"id":"c2","parentId":"p1","content":"Make changes","status":"pending"},{"id":"c3","parentId":"p1","content":"Verify results","status":"pending"}]})`;
295146
295162
  parameters = {
295147
295163
  type: "object",
295148
295164
  required: ["todos"],
@@ -295160,7 +295176,7 @@ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark comp
295160
295176
  type: "string",
295161
295177
  enum: ["pending", "in_progress", "completed", "blocked"]
295162
295178
  },
295163
- parentId: { type: "string", description: "Parent todo id for sub-tasks" },
295179
+ parentId: { type: "string", description: "Parent todo id for nested sub-tasks. Parents summarize objectives; children carry concrete implementation/verification work. Never complete a parent until every child is completed with evidence." },
295164
295180
  blocker: { type: "string", description: "Reason this is blocked (status=blocked only)" },
295165
295181
  verifyCommand: {
295166
295182
  type: "string",
@@ -295277,6 +295293,7 @@ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark comp
295277
295293
  const reminder = "Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Mark the current task in_progress and the next task pending. Proceed with the current task.";
295278
295294
  const payload = {
295279
295295
  reminder,
295296
+ decompositionContract: "Use parentId for sub-todos. Keep parents in_progress/blocked until all children are completed with objective evidence. When tool evidence invalidates a child, rewrite that child as blocked or split it into new children instead of overclaiming completion.",
295280
295297
  oldTodos: result.oldTodos,
295281
295298
  newTodos: result.newTodos,
295282
295299
  verificationNudgeNeeded
@@ -295285,9 +295302,10 @@ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark comp
295285
295302
  payload["inputRepair"] = Array.from(new Set(repairNotes));
295286
295303
  payload["canonicalShape"] = {
295287
295304
  todos: [
295288
- { content: "Inspect files", status: "in_progress" },
295289
- { content: "Make changes", status: "pending" },
295290
- { content: "Verify results", status: "pending" }
295305
+ { id: "p1", content: "Implement the requested change", status: "in_progress" },
295306
+ { id: "c1", parentId: "p1", content: "Inspect files", status: "in_progress" },
295307
+ { id: "c2", parentId: "p1", content: "Make changes", status: "pending" },
295308
+ { id: "c3", parentId: "p1", content: "Verify results", status: "pending" }
295291
295309
  ]
295292
295310
  };
295293
295311
  }
@@ -321828,9 +321846,9 @@ ${lanes.join("\n")}
321828
321846
  function isJsonEqual(a2, b) {
321829
321847
  return a2 === b || typeof a2 === "object" && a2 !== null && typeof b === "object" && b !== null && equalOwnProperties(a2, b, isJsonEqual);
321830
321848
  }
321831
- function parsePseudoBigInt(stringValue2) {
321849
+ function parsePseudoBigInt(stringValue3) {
321832
321850
  let log2Base;
321833
- switch (stringValue2.charCodeAt(1)) {
321851
+ switch (stringValue3.charCodeAt(1)) {
321834
321852
  // "x" in "0x123"
321835
321853
  case 98:
321836
321854
  case 66:
@@ -321845,19 +321863,19 @@ ${lanes.join("\n")}
321845
321863
  log2Base = 4;
321846
321864
  break;
321847
321865
  default:
321848
- const nIndex = stringValue2.length - 1;
321866
+ const nIndex = stringValue3.length - 1;
321849
321867
  let nonZeroStart = 0;
321850
- while (stringValue2.charCodeAt(nonZeroStart) === 48) {
321868
+ while (stringValue3.charCodeAt(nonZeroStart) === 48) {
321851
321869
  nonZeroStart++;
321852
321870
  }
321853
- return stringValue2.slice(nonZeroStart, nIndex) || "0";
321871
+ return stringValue3.slice(nonZeroStart, nIndex) || "0";
321854
321872
  }
321855
- const startIndex = 2, endIndex = stringValue2.length - 1;
321873
+ const startIndex = 2, endIndex = stringValue3.length - 1;
321856
321874
  const bitsNeeded = (endIndex - startIndex) * log2Base;
321857
321875
  const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0));
321858
321876
  for (let i2 = endIndex - 1, bitOffset = 0; i2 >= startIndex; i2--, bitOffset += log2Base) {
321859
321877
  const segment = bitOffset >>> 4;
321860
- const digitChar = stringValue2.charCodeAt(i2);
321878
+ const digitChar = stringValue3.charCodeAt(i2);
321861
321879
  const digit = digitChar <= 57 ? digitChar - 48 : 10 + digitChar - (digitChar <= 70 ? 65 : 97);
321862
321880
  const shiftedDigit = digit << (bitOffset & 15);
321863
321881
  segments[segment] |= shiftedDigit;
@@ -435385,9 +435403,9 @@ ${lanes.join("\n")}
435385
435403
  /*ignoreCase*/
435386
435404
  false
435387
435405
  )) {
435388
- const basename39 = getBaseFileName(a2.fileName);
435389
- if (basename39 === "lib.d.ts" || basename39 === "lib.es6.d.ts") return 0;
435390
- const name10 = removeSuffix(removePrefix(basename39, "lib."), ".d.ts");
435406
+ const basename40 = getBaseFileName(a2.fileName);
435407
+ if (basename40 === "lib.d.ts" || basename40 === "lib.es6.d.ts") return 0;
435408
+ const name10 = removeSuffix(removePrefix(basename40, "lib."), ".d.ts");
435391
435409
  const index = libs.indexOf(name10);
435392
435410
  if (index !== -1) return index + 1;
435393
435411
  }
@@ -499313,8 +499331,8 @@ ${options2.prefix}` : "\n" : options2.prefix
499313
499331
  }
499314
499332
  };
499315
499333
  for (const file of files) {
499316
- const basename39 = getBaseFileName(file);
499317
- if (basename39 === "package.json" || basename39 === "bower.json") {
499334
+ const basename40 = getBaseFileName(file);
499335
+ if (basename40 === "package.json" || basename40 === "bower.json") {
499318
499336
  createProjectWatcher(
499319
499337
  file,
499320
499338
  "FileWatcher"
@@ -502998,8 +503016,8 @@ All files are: ${JSON.stringify(names)}`,
502998
503016
  var _a;
502999
503017
  const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
503000
503018
  if (!fileOrDirectoryPath) return;
503001
- const basename39 = getBaseFileName(fileOrDirectoryPath);
503002
- if (((_a = result.affectedModuleSpecifierCacheProjects) == null ? void 0 : _a.size) && (basename39 === "package.json" || basename39 === "node_modules")) {
503019
+ const basename40 = getBaseFileName(fileOrDirectoryPath);
503020
+ if (((_a = result.affectedModuleSpecifierCacheProjects) == null ? void 0 : _a.size) && (basename40 === "package.json" || basename40 === "node_modules")) {
503003
503021
  result.affectedModuleSpecifierCacheProjects.forEach((project) => {
503004
503022
  var _a2;
503005
503023
  (_a2 = project.getModuleSpecifierCache()) == null ? void 0 : _a2.clear();
@@ -511902,7 +511920,7 @@ var require_path_browserify = __commonJS({
511902
511920
  if (hasRoot && end === 1) return "//";
511903
511921
  return path12.slice(0, end);
511904
511922
  },
511905
- basename: function basename39(path12, ext) {
511923
+ basename: function basename40(path12, ext) {
511906
511924
  if (ext !== void 0 && typeof ext !== "string") throw new TypeError('"ext" argument must be a string');
511907
511925
  assertPath(path12);
511908
511926
  var start2 = 0;
@@ -546577,6 +546595,32 @@ ${content.slice(0, 500)}`,
546577
546595
  }
546578
546596
  });
546579
546597
 
546598
+ // packages/execution/dist/tools/clip-feature-python.js
546599
+ var CLIP_FEATURE_HELPERS_PY;
546600
+ var init_clip_feature_python = __esm({
546601
+ "packages/execution/dist/tools/clip-feature-python.js"() {
546602
+ "use strict";
546603
+ CLIP_FEATURE_HELPERS_PY = `
546604
+ def _omnius_feature_tensor(features):
546605
+ if hasattr(features, "image_embeds"):
546606
+ return features.image_embeds
546607
+ if hasattr(features, "text_embeds"):
546608
+ return features.text_embeds
546609
+ if hasattr(features, "pooler_output"):
546610
+ return features.pooler_output
546611
+ if hasattr(features, "last_hidden_state"):
546612
+ return features.last_hidden_state[:, 0]
546613
+ if isinstance(features, (tuple, list)):
546614
+ return features[0]
546615
+ return features
546616
+
546617
+ def _omnius_normalized_features(features):
546618
+ tensor = _omnius_feature_tensor(features)
546619
+ return tensor / tensor.norm(dim=-1, keepdim=True)
546620
+ `;
546621
+ }
546622
+ });
546623
+
546580
546624
  // packages/execution/dist/tools/visual-memory.js
546581
546625
  import { execSync as execSync39 } from "node:child_process";
546582
546626
  import { existsSync as existsSync66, mkdirSync as mkdirSync39, writeFileSync as writeFileSync31, readFileSync as readFileSync48 } from "node:fs";
@@ -546587,33 +546631,123 @@ function visualMemoryPythonEnv(extra = {}) {
546587
546631
  applyMediaCudaDeviceFilterToEnv(env2, "vision");
546588
546632
  return env2;
546589
546633
  }
546590
- var VMEM_DIR, VENV_DIR2, VENV_PY, VENV_PIP2, VisualMemoryTool;
546634
+ function stringArg2(args, ...keys) {
546635
+ for (const key of keys) {
546636
+ const value2 = args[key];
546637
+ if (typeof value2 === "string" && value2.trim())
546638
+ return value2.trim();
546639
+ }
546640
+ return "";
546641
+ }
546642
+ function stringListArg(value2) {
546643
+ if (Array.isArray(value2)) {
546644
+ return value2.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
546645
+ }
546646
+ if (typeof value2 === "string" && value2.trim())
546647
+ return [value2.trim()];
546648
+ return [];
546649
+ }
546650
+ function imagePathArg(args) {
546651
+ return stringArg2(args, "image", "path", "file", "media");
546652
+ }
546653
+ function associationLabelArg(args) {
546654
+ return stringArg2(args, "name", "label", "title", "object", "person") || stringListArg(args["labels"])[0] || stringListArg(args["aliases"])[0] || "";
546655
+ }
546656
+ function pyLiteral(value2) {
546657
+ return JSON.stringify(value2);
546658
+ }
546659
+ function normalizeVisualMemoryAction(args) {
546660
+ const raw = stringArg2(args, "action").toLowerCase();
546661
+ if (!raw) {
546662
+ if (imagePathArg(args) && associationLabelArg(args))
546663
+ return "teach";
546664
+ if (imagePathArg(args))
546665
+ return "recognize";
546666
+ return "";
546667
+ }
546668
+ if (raw === "learn" || raw === "remember" || raw === "associate")
546669
+ return "teach";
546670
+ if (raw === "score" || raw === "classify")
546671
+ return "describe";
546672
+ return raw;
546673
+ }
546674
+ function summarizeProcessFailure(stdout, stderr) {
546675
+ const normalize2 = (text2) => text2.replace(/\r/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean);
546676
+ const stderrLines = normalize2(stderr);
546677
+ const stdoutLines = normalize2(stdout);
546678
+ const tagged = [
546679
+ ...stderrLines.map((line) => `stderr: ${line}`),
546680
+ ...stdoutLines.map((line) => `stdout: ${line}`)
546681
+ ];
546682
+ if (tagged.length === 0)
546683
+ return "Vision ML script failed";
546684
+ const exceptionRe = /(?:^|\s)(?:[A-Za-z_][\w.]*Error|[A-Za-z_][\w.]*Exception|AssertionError|KeyboardInterrupt|SystemExit):\s+.+$/;
546685
+ const tracebackIdx = tagged.findIndex((line) => /Traceback \(most recent call last\):/.test(line));
546686
+ const exceptionLine = [...stderrLines, ...stdoutLines].slice().reverse().find((line) => exceptionRe.test(line) && !/\bwarning:/i.test(line));
546687
+ const parts = [];
546688
+ if (exceptionLine) {
546689
+ parts.push(`Root cause: ${exceptionLine}`);
546690
+ }
546691
+ if (tracebackIdx >= 0) {
546692
+ parts.push("Traceback:");
546693
+ parts.push(...tagged.slice(tracebackIdx).slice(0, 18));
546694
+ }
546695
+ const tail = tagged.slice(-24);
546696
+ for (const line of tail) {
546697
+ if (!parts.includes(line))
546698
+ parts.push(line);
546699
+ }
546700
+ return parts.join("\n").slice(0, 2200);
546701
+ }
546702
+ var VMEM_DIR, VENV_DIR2, VENV_PY, VENV_PIP2, VISUAL_MEMORY_ACTIONS, VisualMemoryTool;
546591
546703
  var init_visual_memory = __esm({
546592
546704
  "packages/execution/dist/tools/visual-memory.js"() {
546593
546705
  "use strict";
546594
546706
  init_cuda_device_filter();
546707
+ init_clip_feature_python();
546595
546708
  VMEM_DIR = join80(homedir20(), ".omnius", "visual-memory");
546596
546709
  VENV_DIR2 = join80(homedir20(), ".omnius", "vision-ml-venv");
546597
546710
  VENV_PY = join80(VENV_DIR2, "bin", "python3");
546598
546711
  VENV_PIP2 = join80(VENV_DIR2, "bin", "pip");
546712
+ VISUAL_MEMORY_ACTIONS = /* @__PURE__ */ new Set(["detect", "enroll", "identify", "teach", "recognize", "describe", "list", "forget"]);
546599
546713
  VisualMemoryTool = class {
546600
546714
  name = "visual_memory";
546601
- description = "Persistent visual memory — recognize faces and objects across sessions. FACE actions: 'detect' faces in image, 'enroll' a person (name + photo), 'identify' known faces. OBJECT actions: 'teach' an object (label + photo), 'recognize' taught objects. MEMORY actions: 'list' all enrolled faces/objects, 'forget' a person/object. Use this to remember people, learn to recognize tools/items, and build persistent visual knowledge about the physical environment. Works with camera_capture output.";
546715
+ description = "Persistent visual memory — automatically associate names/labels with images and recognize familiar faces/objects across sessions. When a user, filename, manifest, or surrounding text provides a name or label for a new image, call teach/enroll immediately to store that association. If an image is unlabeled, call vision first for a Moondream caption, then recognize/identify against this memory; ask the user for a label only when the identity is still unknown. FACE actions: 'detect' faces in image, 'enroll' a person (name + photo), 'identify' known faces. OBJECT actions: 'teach' an object (label + photo), 'recognize' taught objects, 'describe' CLIP scores for candidate labels. MEMORY actions: 'list' all enrolled faces/objects, 'forget' a person/object. Use this to remember people, learn to recognize tools/items, and build persistent visual knowledge about the physical environment. Works with camera_capture output.";
546602
546716
  parameters = {
546603
546717
  type: "object",
546604
546718
  properties: {
546605
546719
  action: {
546606
546720
  type: "string",
546607
- enum: ["detect", "enroll", "identify", "teach", "recognize", "list", "forget"],
546608
- description: "Action: detect/enroll/identify faces, teach/recognize objects, list/forget memory"
546721
+ enum: ["detect", "enroll", "identify", "teach", "recognize", "describe", "list", "forget"],
546722
+ description: "Action: detect/enroll/identify faces, teach/recognize/describe objects, list/forget memory. If omitted with image+label/name, teach is inferred; with image only, recognize is inferred."
546609
546723
  },
546610
546724
  image: {
546611
546725
  type: "string",
546612
- description: "Path to image file (JPEG/PNG). Required for detect/enroll/identify/teach/recognize."
546726
+ description: "Path to image file (JPEG/PNG). Required for detect/enroll/identify/teach/recognize/describe."
546727
+ },
546728
+ path: {
546729
+ type: "string",
546730
+ description: "Alias for image path."
546731
+ },
546732
+ file: {
546733
+ type: "string",
546734
+ description: "Alias for image path."
546735
+ },
546736
+ media: {
546737
+ type: "string",
546738
+ description: "Alias for image path."
546613
546739
  },
546614
546740
  name: {
546615
546741
  type: "string",
546616
- description: "Person name (for 'enroll') or object label (for 'teach'). Required for enrollment."
546742
+ description: "Person name (for 'enroll') or object label (for 'teach')."
546743
+ },
546744
+ label: {
546745
+ type: "string",
546746
+ description: "Alias for name when teaching an object or enrolling a person from an associated label."
546747
+ },
546748
+ title: {
546749
+ type: "string",
546750
+ description: "Alias for name/label when a manifest or metadata title identifies the visual subject."
546617
546751
  },
546618
546752
  id: {
546619
546753
  type: "string",
@@ -546630,15 +546764,23 @@ var init_visual_memory = __esm({
546630
546764
  },
546631
546765
  labels: {
546632
546766
  type: "array",
546633
- description: "Text labels to score against image (for CLIP 'recognize' or 'describe').",
546767
+ description: "Candidate text labels to score against image for CLIP recognize/describe. For teach, labels[0] is accepted as the object label when name/label is omitted.",
546768
+ items: { type: "string" }
546769
+ },
546770
+ aliases: {
546771
+ type: "array",
546772
+ description: "Additional label aliases. aliases[0] is accepted as the primary label when name/label/labels are omitted.",
546634
546773
  items: { type: "string" }
546635
546774
  }
546636
546775
  },
546637
- required: ["action"]
546776
+ required: []
546638
546777
  };
546639
546778
  async execute(args) {
546640
- const action = args["action"];
546779
+ const action = normalizeVisualMemoryAction(args);
546641
546780
  const start2 = performance.now();
546781
+ if (!VISUAL_MEMORY_ACTIONS.has(action)) {
546782
+ return { success: false, output: "", error: `Unknown action: ${action || "(none)"}. Use teach with image+name/label to remember, recognize with image to recall, or vision first for unlabeled semantic captioning.`, durationMs: performance.now() - start2 };
546783
+ }
546642
546784
  if (!await this.ensureVenv()) {
546643
546785
  return { success: false, output: "", error: "Could not set up vision ML environment. Needs Python 3.10+.", durationMs: performance.now() - start2 };
546644
546786
  }
@@ -546656,6 +546798,11 @@ var init_visual_memory = __esm({
546656
546798
  return await this.teachObject(args, start2);
546657
546799
  case "recognize":
546658
546800
  return await this.recognizeObjects(args, start2);
546801
+ case "describe":
546802
+ return await this.recognizeObjects({
546803
+ ...args,
546804
+ threshold: typeof args["threshold"] === "number" ? args["threshold"] : -1
546805
+ }, start2);
546659
546806
  case "list":
546660
546807
  return await this.listMemory(start2);
546661
546808
  case "forget":
@@ -546671,7 +546818,7 @@ var init_visual_memory = __esm({
546671
546818
  // Face Detection
546672
546819
  // =========================================================================
546673
546820
  async detectFaces(args, start2) {
546674
- const image = args["image"];
546821
+ const image = imagePathArg(args);
546675
546822
  if (!image || !existsSync66(image)) {
546676
546823
  return { success: false, output: "", error: "Provide a valid image path.", durationMs: performance.now() - start2 };
546677
546824
  }
@@ -546684,7 +546831,7 @@ app = FaceAnalysis(name='buffalo_s', providers=['CUDAExecutionProvider', 'CPUExe
546684
546831
  app.prepare(ctx_id=0, det_size=(640, 640))
546685
546832
 
546686
546833
  import cv2
546687
- img = cv2.imread("${image}")
546834
+ img = cv2.imread(${pyLiteral(image)})
546688
546835
  if img is None:
546689
546836
  print(json.dumps({"success": False, "error": "Could not read image"}))
546690
546837
  sys.exit(0)
@@ -546716,8 +546863,8 @@ ${JSON.stringify(result.faces, null, 2)}`, durationMs: performance.now() - start
546716
546863
  // Face Enrollment
546717
546864
  // =========================================================================
546718
546865
  async enrollFace(args, start2) {
546719
- const image = args["image"];
546720
- const name10 = args["name"];
546866
+ const image = imagePathArg(args);
546867
+ const name10 = associationLabelArg(args);
546721
546868
  if (!image || !existsSync66(image))
546722
546869
  return { success: false, output: "", error: "Provide a valid image path.", durationMs: performance.now() - start2 };
546723
546870
  if (!name10)
@@ -546730,7 +546877,7 @@ app = FaceAnalysis(name='buffalo_s', providers=['CUDAExecutionProvider', 'CPUExe
546730
546877
  app.prepare(ctx_id=0, det_size=(640, 640))
546731
546878
 
546732
546879
  import cv2
546733
- img = cv2.imread("${image}")
546880
+ img = cv2.imread(${pyLiteral(image)})
546734
546881
  if img is None:
546735
546882
  print(json.dumps({"success": False, "error": "Could not read image"}))
546736
546883
  sys.exit(0)
@@ -546751,9 +546898,9 @@ if os.path.exists(db_path):
546751
546898
  with open(db_path) as f:
546752
546899
  db = json.load(f)
546753
546900
 
546754
- person_id = "${name10}".lower().replace(" ", "_").replace("'", "")
546901
+ person_id = ${pyLiteral(name10)}.lower().replace(" ", "_").replace("'", "")
546755
546902
  if person_id not in db:
546756
- db[person_id] = {"name": "${name10}", "embeddings": [], "created_at": time.time()}
546903
+ db[person_id] = {"name": ${pyLiteral(name10)}, "embeddings": [], "created_at": time.time()}
546757
546904
 
546758
546905
  db[person_id]["embeddings"].append(embedding)
546759
546906
  db[person_id]["updated_at"] = time.time()
@@ -546765,7 +546912,7 @@ with open(db_path, "w") as f:
546765
546912
  print(json.dumps({
546766
546913
  "success": True,
546767
546914
  "person_id": person_id,
546768
- "name": "${name10}",
546915
+ "name": ${pyLiteral(name10)},
546769
546916
  "samples": sample_count,
546770
546917
  "total_people": len(db),
546771
546918
  "confidence": float(face.det_score),
@@ -546786,7 +546933,7 @@ print(json.dumps({
546786
546933
  // Face Identification
546787
546934
  // =========================================================================
546788
546935
  async identifyFaces(args, start2) {
546789
- const image = args["image"];
546936
+ const image = imagePathArg(args);
546790
546937
  if (!image || !existsSync66(image))
546791
546938
  return { success: false, output: "", error: "Provide a valid image path.", durationMs: performance.now() - start2 };
546792
546939
  const threshold = args["threshold"] || 0.5;
@@ -546799,7 +546946,7 @@ app = FaceAnalysis(name='buffalo_s', providers=['CUDAExecutionProvider', 'CPUExe
546799
546946
  app.prepare(ctx_id=0, det_size=(640, 640))
546800
546947
 
546801
546948
  import cv2
546802
- img = cv2.imread("${image}")
546949
+ img = cv2.imread(${pyLiteral(image)})
546803
546950
  if img is None:
546804
546951
  print(json.dumps({"success": False, "error": "Could not read image"}))
546805
546952
  sys.exit(0)
@@ -546883,37 +547030,51 @@ ${lines.join("\n")}`,
546883
547030
  // Object Teaching (CLIP/SigLIP)
546884
547031
  // =========================================================================
546885
547032
  async teachObject(args, start2) {
546886
- const image = args["image"];
546887
- const label = args["name"];
547033
+ const image = imagePathArg(args);
547034
+ const label = associationLabelArg(args);
546888
547035
  if (!image || !existsSync66(image))
546889
547036
  return { success: false, output: "", error: "Provide a valid image path.", durationMs: performance.now() - start2 };
546890
547037
  if (!label)
546891
547038
  return { success: false, output: "", error: "Provide a label for the object.", durationMs: performance.now() - start2 };
547039
+ const aliases = [...new Set([
547040
+ label,
547041
+ ...stringListArg(args["labels"]),
547042
+ ...stringListArg(args["aliases"])
547043
+ ].map((item) => item.trim()).filter(Boolean))];
546892
547044
  const result = await this.runVisionPython(`
546893
- import json, sys, os, time
547045
+ import json, sys, os, time, hashlib
546894
547046
  import torch
546895
547047
  from PIL import Image
546896
547048
  from transformers import CLIPProcessor, CLIPModel
546897
547049
 
547050
+ ${CLIP_FEATURE_HELPERS_PY}
547051
+
547052
+ image_path = ${pyLiteral(image)}
547053
+ label = ${pyLiteral(label)}
547054
+ aliases = ${JSON.stringify(aliases)}
547055
+ with open(image_path, "rb") as f:
547056
+ image_hash = hashlib.sha256(f.read()).hexdigest()
547057
+
546898
547058
  # Use CLIP ViT-B/32 — good balance of speed and quality
546899
547059
  model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
546900
547060
  processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
546901
547061
 
546902
- img = Image.open("${image}").convert("RGB")
547062
+ img = Image.open(image_path).convert("RGB")
546903
547063
  inputs = processor(images=img, return_tensors="pt")
546904
547064
  with torch.no_grad():
546905
- image_features = model.get_image_features(**inputs)
546906
- image_features = image_features / image_features.norm(dim=-1, keepdim=True)
547065
+ image_features = _omnius_normalized_features(model.get_image_features(**inputs))
546907
547066
 
546908
547067
  embedding = image_features[0].cpu().numpy().tolist()
546909
547068
 
546910
- # Also embed the text label for cross-modal retrieval
546911
- text_inputs = processor(text=["${label}"], return_tensors="pt", padding=True)
547069
+ # Also embed the text label and aliases for cross-modal retrieval
547070
+ text_inputs = processor(text=aliases, return_tensors="pt", padding=True)
546912
547071
  with torch.no_grad():
546913
- text_features = model.get_text_features(**text_inputs)
546914
- text_features = text_features / text_features.norm(dim=-1, keepdim=True)
547072
+ text_features = _omnius_normalized_features(model.get_text_features(**text_inputs))
546915
547073
 
546916
- text_embedding = text_features[0].cpu().numpy().tolist()
547074
+ text_embeddings = {}
547075
+ for i, alias in enumerate(aliases):
547076
+ text_embeddings[alias] = text_features[i].cpu().numpy().tolist()
547077
+ text_embedding = text_embeddings[aliases[0]]
546917
547078
 
546918
547079
  # Store in object database
546919
547080
  db_path = "${VMEM_DIR}/objects.json"
@@ -546922,12 +547083,19 @@ if os.path.exists(db_path):
546922
547083
  with open(db_path) as f:
546923
547084
  db = json.load(f)
546924
547085
 
546925
- obj_id = "${label}".lower().replace(" ", "_").replace("'", "")
547086
+ obj_id = label.lower().replace(" ", "_").replace("'", "")
546926
547087
  if obj_id not in db:
546927
- db[obj_id] = {"label": "${label}", "image_embeddings": [], "text_embedding": text_embedding, "created_at": time.time()}
547088
+ db[obj_id] = {"label": label, "aliases": [], "image_embeddings": [], "image_hashes": [], "text_embeddings": {}, "text_embedding": text_embedding, "created_at": time.time()}
546928
547089
 
546929
- db[obj_id]["image_embeddings"].append(embedding)
547090
+ existing_hashes = set(db[obj_id].get("image_hashes", []))
547091
+ duplicate_sample = image_hash in existing_hashes
547092
+ if not duplicate_sample:
547093
+ db[obj_id].setdefault("image_embeddings", []).append(embedding)
547094
+ db[obj_id].setdefault("image_hashes", []).append(image_hash)
546930
547095
  db[obj_id]["text_embedding"] = text_embedding # update with latest
547096
+ db[obj_id].setdefault("text_embeddings", {}).update(text_embeddings)
547097
+ merged_aliases = list(dict.fromkeys([db[obj_id].get("label", label)] + db[obj_id].get("aliases", []) + aliases))
547098
+ db[obj_id]["aliases"] = merged_aliases
546931
547099
  db[obj_id]["updated_at"] = time.time()
546932
547100
  sample_count = len(db[obj_id]["image_embeddings"])
546933
547101
 
@@ -546937,10 +547105,12 @@ with open(db_path, "w") as f:
546937
547105
  print(json.dumps({
546938
547106
  "success": True,
546939
547107
  "object_id": obj_id,
546940
- "label": "${label}",
547108
+ "label": label,
547109
+ "aliases": merged_aliases,
546941
547110
  "samples": sample_count,
546942
547111
  "total_objects": len(db),
546943
547112
  "embedding_dim": len(embedding),
547113
+ "duplicate_sample": duplicate_sample,
546944
547114
  }))
546945
547115
  `, 12e4);
546946
547116
  if (!result.success)
@@ -546949,7 +547119,9 @@ print(json.dumps({
546949
547119
  success: true,
546950
547120
  output: `Taught object "${result.label}" (ID: ${result.object_id})
546951
547121
  Samples: ${result.samples}
546952
- Embedding: ${result.embedding_dim}d CLIP vector
547122
+ Aliases: ${(result.aliases || []).join(", ") || result.label}
547123
+ ` + (result.duplicate_sample ? ` Duplicate sample skipped for same image hash
547124
+ ` : "") + ` Embedding: ${result.embedding_dim}d CLIP vector
546953
547125
  Total objects in memory: ${result.total_objects}`,
546954
547126
  durationMs: performance.now() - start2
546955
547127
  };
@@ -546958,11 +547130,11 @@ print(json.dumps({
546958
547130
  // Object Recognition
546959
547131
  // =========================================================================
546960
547132
  async recognizeObjects(args, start2) {
546961
- const image = args["image"];
547133
+ const image = imagePathArg(args);
546962
547134
  if (!image || !existsSync66(image))
546963
547135
  return { success: false, output: "", error: "Provide a valid image path.", durationMs: performance.now() - start2 };
546964
- const threshold = args["threshold"] || 0.3;
546965
- const extraLabels = args["labels"] || [];
547136
+ const threshold = typeof args["threshold"] === "number" ? args["threshold"] : 0.3;
547137
+ const extraLabels = stringListArg(args["labels"]);
546966
547138
  const wantsJson = args["format"] === "json" || args["json"] === true;
546967
547139
  const result = await this.runVisionPython(`
546968
547140
  import json, sys, os, numpy as np
@@ -546970,16 +547142,17 @@ import torch
546970
547142
  from PIL import Image
546971
547143
  from transformers import CLIPProcessor, CLIPModel
546972
547144
 
547145
+ ${CLIP_FEATURE_HELPERS_PY}
547146
+
546973
547147
  model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
546974
547148
  processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
546975
547149
 
546976
- img = Image.open("${image}").convert("RGB")
547150
+ img = Image.open(${pyLiteral(image)}).convert("RGB")
546977
547151
 
546978
547152
  # Get image embedding
546979
547153
  inputs = processor(images=img, return_tensors="pt")
546980
547154
  with torch.no_grad():
546981
- image_features = model.get_image_features(**inputs)
546982
- image_features = image_features / image_features.norm(dim=-1, keepdim=True)
547155
+ image_features = _omnius_normalized_features(model.get_image_features(**inputs))
546983
547156
 
546984
547157
  query = image_features[0].cpu().numpy()
546985
547158
 
@@ -546994,13 +547167,30 @@ if os.path.exists(db_path):
546994
547167
  matches = []
546995
547168
  for obj_id, data in db.items():
546996
547169
  best_sim = -1
546997
- for emb in data["image_embeddings"]:
547170
+ for emb in data.get("image_embeddings", []):
546998
547171
  sim = float(np.dot(query, np.array(emb, dtype=np.float32)))
546999
547172
  if sim > best_sim:
547000
547173
  best_sim = sim
547001
547174
 
547002
- # Also score against text label embedding
547003
- text_sim = float(np.dot(query, np.array(data["text_embedding"], dtype=np.float32)))
547175
+ # Also score against all stored text label/alias embeddings
547176
+ text_sim = -1
547177
+ best_text_label = data.get("label", obj_id)
547178
+ text_embeddings = data.get("text_embeddings")
547179
+ if isinstance(text_embeddings, dict) and text_embeddings:
547180
+ iterable = text_embeddings.items()
547181
+ elif data.get("text_embedding") is not None:
547182
+ iterable = [(data.get("label", obj_id), data["text_embedding"])]
547183
+ else:
547184
+ iterable = []
547185
+ for alias, emb in iterable:
547186
+ sim = float(np.dot(query, np.array(emb, dtype=np.float32)))
547187
+ if sim > text_sim:
547188
+ text_sim = sim
547189
+ best_text_label = alias
547190
+ if text_sim < 0:
547191
+ text_sim = 0
547192
+ if best_sim < 0:
547193
+ best_sim = 0
547004
547194
 
547005
547195
  # Blend: 60% image similarity, 40% text similarity
547006
547196
  blended = 0.6 * best_sim + 0.4 * text_sim
@@ -547008,6 +547198,8 @@ for obj_id, data in db.items():
547008
547198
  matches.append({
547009
547199
  "object_id": obj_id,
547010
547200
  "label": data["label"],
547201
+ "matched_alias": best_text_label,
547202
+ "aliases": data.get("aliases", []),
547011
547203
  "image_similarity": round(best_sim, 3),
547012
547204
  "text_similarity": round(text_sim, 3),
547013
547205
  "blended_score": round(blended, 3),
@@ -547022,8 +547214,7 @@ extra_labels = ${JSON.stringify(extraLabels)}
547022
547214
  if extra_labels:
547023
547215
  text_inputs = processor(text=extra_labels, return_tensors="pt", padding=True)
547024
547216
  with torch.no_grad():
547025
- text_features = model.get_text_features(**text_inputs)
547026
- text_features = text_features / text_features.norm(dim=-1, keepdim=True)
547217
+ text_features = _omnius_normalized_features(model.get_text_features(**text_inputs))
547027
547218
 
547028
547219
  for i, label in enumerate(extra_labels):
547029
547220
  sim = float(np.dot(query, text_features[i].cpu().numpy()))
@@ -547089,7 +547280,7 @@ ${objects.join("\n") || " (none taught)"}`,
547089
547280
  };
547090
547281
  }
547091
547282
  async forgetEntry(args, start2) {
547092
- const id = (args["id"] || args["name"] || "").toLowerCase().replace(/\s/g, "_");
547283
+ const id = stringArg2(args, "id", "name", "label").toLowerCase().replace(/\s/g, "_");
547093
547284
  if (!id)
547094
547285
  return { success: false, output: "", error: "Provide the ID or name of the person/object to forget.", durationMs: performance.now() - start2 };
547095
547286
  let removed = false;
@@ -547165,7 +547356,7 @@ ${objects.join("\n") || " (none taught)"}`,
547165
547356
  }
547166
547357
  }
547167
547358
  }
547168
- throw new Error(stderr.slice(0, 300) || stdout.slice(0, 300) || "Vision ML script failed");
547359
+ throw new Error(summarizeProcessFailure(stdout, stderr));
547169
547360
  }
547170
547361
  }
547171
547362
  };
@@ -547182,6 +547373,7 @@ var MM_DIR, MM_INDEX, MultimodalMemoryTool;
547182
547373
  var init_multimodal_memory = __esm({
547183
547374
  "packages/execution/dist/tools/multimodal-memory.js"() {
547184
547375
  "use strict";
547376
+ init_clip_feature_python();
547185
547377
  MM_DIR = join81(homedir21(), ".omnius", "multimodal-episodes");
547186
547378
  MM_INDEX = join81(MM_DIR, "index.json");
547187
547379
  MultimodalMemoryTool = class {
@@ -547275,13 +547467,13 @@ var init_multimodal_memory = __esm({
547275
547467
  import json, torch
547276
547468
  from PIL import Image
547277
547469
  from transformers import CLIPProcessor, CLIPModel
547470
+ ${CLIP_FEATURE_HELPERS_PY}
547278
547471
  model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
547279
547472
  processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
547280
547473
  img = Image.open("${imagePath}").convert("RGB")
547281
547474
  inputs = processor(images=img, return_tensors="pt")
547282
547475
  with torch.no_grad():
547283
- features = model.get_image_features(**inputs)
547284
- features = features / features.norm(dim=-1, keepdim=True)
547476
+ features = _omnius_normalized_features(model.get_image_features(**inputs))
547285
547477
  print(json.dumps(features[0].cpu().numpy().tolist()))
547286
547478
  `;
547287
547479
  const scriptFile = join81(tmpdir17(), `mm-clip-${Date.now()}.py`);
@@ -547501,12 +547693,12 @@ Recall later: multimodal_memory action=recall query="${personName}"`,
547501
547693
  const clipTextScript = `
547502
547694
  import json, torch
547503
547695
  from transformers import CLIPProcessor, CLIPModel
547696
+ ${CLIP_FEATURE_HELPERS_PY}
547504
547697
  model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
547505
547698
  processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
547506
547699
  inputs = processor(text=["${query.replace(/"/g, '\\"').replace(/\n/g, " ")}"], return_tensors="pt", padding=True)
547507
547700
  with torch.no_grad():
547508
- features = model.get_text_features(**inputs)
547509
- features = features / features.norm(dim=-1, keepdim=True)
547701
+ features = _omnius_normalized_features(model.get_text_features(**inputs))
547510
547702
  print(json.dumps(features[0].cpu().numpy().tolist()))
547511
547703
  `;
547512
547704
  const scriptFile = join81(tmpdir17(), `mm-clipq-${Date.now()}.py`);
@@ -558355,6 +558547,15 @@ function nowIso2(now2 = /* @__PURE__ */ new Date()) {
558355
558547
  function cleanText(value2, max = 600) {
558356
558548
  return String(value2 ?? "").replace(/\s+/g, " ").trim().slice(0, max);
558357
558549
  }
558550
+ function normalizeEvidencePath(value2) {
558551
+ return String(value2 ?? "").trim().replace(/^file:\/\//i, "").replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\.\//, "");
558552
+ }
558553
+ function cleanTargetPaths(paths) {
558554
+ if (!Array.isArray(paths))
558555
+ return [];
558556
+ const cleaned = paths.map(normalizeEvidencePath).filter((path12) => path12.length > 0).slice(0, 20);
558557
+ return [...new Set(cleaned)];
558558
+ }
558358
558559
  function convertPipesToUnicode(text2) {
558359
558560
  return text2.replace(/\|/g, "│");
558360
558561
  }
@@ -558430,6 +558631,7 @@ function deriveClaimsFromProposedText(input) {
558430
558631
  });
558431
558632
  }
558432
558633
  function recordCompletionEvidence(ledger, evidence) {
558634
+ const targetPaths = cleanTargetPaths(evidence.targetPaths);
558433
558635
  const entry = {
558434
558636
  id: evidence.id ?? nextId2("ev", ledger.evidence.length),
558435
558637
  kind: evidence.kind,
@@ -558437,7 +558639,8 @@ function recordCompletionEvidence(ledger, evidence) {
558437
558639
  summary: cleanText(evidence.summary, 1200),
558438
558640
  toolName: evidence.toolName,
558439
558641
  success: evidence.success,
558440
- rawRef: evidence.rawRef
558642
+ rawRef: evidence.rawRef,
558643
+ ...targetPaths.length > 0 ? { targetPaths } : {}
558441
558644
  };
558442
558645
  return {
558443
558646
  ...ledger,
@@ -558457,7 +558660,8 @@ function recordToolEvidence(ledger, input) {
558457
558660
  toolName: input.name,
558458
558661
  success: input.success,
558459
558662
  summary,
558460
- rawRef: input.rawRef
558663
+ rawRef: input.rawRef,
558664
+ targetPaths: input.targetPaths
558461
558665
  });
558462
558666
  }
558463
558667
  function addCompletionClaims(ledger, claims) {
@@ -558596,6 +558800,55 @@ function isMutationEvidence(entry) {
558596
558800
  return true;
558597
558801
  return entry.success === true && /^(file_write|file_edit|file_patch|batch_edit)$/.test(entry.toolName ?? "");
558598
558802
  }
558803
+ function evidenceTargetPaths(entry) {
558804
+ const explicit = cleanTargetPaths(entry.targetPaths);
558805
+ if (explicit.length > 0)
558806
+ return explicit;
558807
+ const paths = [];
558808
+ if (entry.rawRef?.startsWith("file://")) {
558809
+ paths.push(entry.rawRef.slice("file://".length));
558810
+ }
558811
+ const fileChange = entry.summary.match(/\bfile change:\s*([^\s,;]+)/i);
558812
+ if (fileChange?.[1])
558813
+ paths.push(fileChange[1]);
558814
+ const quotedPathRe = /"(?:path|file_path|file|filename|filepath|filePath)"\s*:\s*"([^"]+)"/g;
558815
+ for (const match of entry.summary.matchAll(quotedPathRe)) {
558816
+ if (match[1])
558817
+ paths.push(match[1]);
558818
+ }
558819
+ const loosePathRe = /\b(?:path|file_path|file|filename|filepath|filePath)=([^,\s}\]]+)/g;
558820
+ for (const match of entry.summary.matchAll(loosePathRe)) {
558821
+ if (match[1])
558822
+ paths.push(match[1].replace(/^["']|["']$/g, ""));
558823
+ }
558824
+ return cleanTargetPaths(paths);
558825
+ }
558826
+ function pathsEquivalent(a2, b) {
558827
+ const left = normalizeEvidencePath(a2);
558828
+ const right = normalizeEvidencePath(b);
558829
+ if (!left || !right)
558830
+ return false;
558831
+ return left === right || left.endsWith(`/${right}`) || right.endsWith(`/${left}`);
558832
+ }
558833
+ function isVerificationNeutralPath(path12) {
558834
+ const normalized = normalizeEvidencePath(path12).toLowerCase();
558835
+ const base3 = normalized.split("/").pop() ?? normalized;
558836
+ if (/\.(md|mdx|markdown|txt|rst|adoc)$/.test(base3))
558837
+ return true;
558838
+ if (/^(readme|changelog|changes|license|licence|notice|implementation_notes)(?:\.[^.]+)?$/.test(base3))
558839
+ return true;
558840
+ if (normalized.includes("/docs/") || normalized.startsWith("docs/"))
558841
+ return true;
558842
+ return false;
558843
+ }
558844
+ function isVerificationInvalidatingMutation(entry) {
558845
+ if (!isMutationEvidence(entry))
558846
+ return false;
558847
+ const paths = evidenceTargetPaths(entry);
558848
+ if (paths.length === 0)
558849
+ return true;
558850
+ return paths.some((path12) => !isVerificationNeutralPath(path12));
558851
+ }
558599
558852
  function isSuccessfulVerificationEvidence(entry) {
558600
558853
  if (entry.success !== true)
558601
558854
  return false;
@@ -558627,14 +558880,72 @@ function isStaleEditEvidence(entry) {
558627
558880
  const text2 = entry.summary.toLowerCase();
558628
558881
  return /stale edit loop blocked|old[_ -]?string|old text|expected[_ -]?hash|context mismatch|no occurrences|could not find/.test(text2);
558629
558882
  }
558883
+ function staleEditResolvedByLaterMutation(evidence, failedEntry, failedIndex) {
558884
+ const failedPaths = evidenceTargetPaths(failedEntry);
558885
+ if (failedPaths.length === 0)
558886
+ return false;
558887
+ for (const later of evidence.slice(failedIndex + 1)) {
558888
+ if (!isMutationEvidence(later) || later.success !== true)
558889
+ continue;
558890
+ const laterPaths = evidenceTargetPaths(later);
558891
+ if (laterPaths.some((laterPath) => failedPaths.some((failedPath) => pathsEquivalent(laterPath, failedPath)))) {
558892
+ return true;
558893
+ }
558894
+ }
558895
+ return false;
558896
+ }
558897
+ function extractClaimedAllTestsPassedCount(text2) {
558898
+ const match = text2.match(/\ball\s+(\d{1,6})\s+tests?\s+(?:pass|passed|passing)\b/i);
558899
+ return match?.[1] ? Number(match[1]) : null;
558900
+ }
558901
+ function extractObservedSuccessfulTestCount(text2) {
558902
+ const normalized = text2.replace(/\s+/g, " ");
558903
+ const testsPassed = normalized.match(/\btests?\s+(\d{1,6})\s+passed\b/i);
558904
+ if (testsPassed?.[1])
558905
+ return Number(testsPassed[1]);
558906
+ const testsLine = normalized.match(/\btests?\s*:?\s*(\d{1,6})\b/i);
558907
+ const passLine = normalized.match(/\bpass(?:ed)?\s*:?\s*(\d{1,6})\b/i);
558908
+ if (testsLine?.[1])
558909
+ return Number(testsLine[1]);
558910
+ if (passLine?.[1])
558911
+ return Number(passLine[1]);
558912
+ const passedTests = normalized.match(/\b(\d{1,6})\s+tests?\s+passed\b/i);
558913
+ if (passedTests?.[1])
558914
+ return Number(passedTests[1]);
558915
+ return null;
558916
+ }
558917
+ function latestObservedSuccessfulTestCount(evidence) {
558918
+ for (let index = evidence.length - 1; index >= 0; index--) {
558919
+ const entry = evidence[index];
558920
+ if (entry.success !== true || verificationFamily(entry) !== "test")
558921
+ continue;
558922
+ const count = extractObservedSuccessfulTestCount(entry.summary);
558923
+ if (count !== null && Number.isFinite(count))
558924
+ return count;
558925
+ }
558926
+ return null;
558927
+ }
558928
+ function appendTestCountOverclaims(ledger, unresolved) {
558929
+ const observed = latestObservedSuccessfulTestCount(ledger.evidence);
558930
+ if (observed === null)
558931
+ return unresolved;
558932
+ let next = unresolved;
558933
+ for (const claim of ledger.proposedClaims) {
558934
+ const claimed = extractClaimedAllTestsPassedCount(claim.text);
558935
+ if (claimed === null || claimed === observed)
558936
+ continue;
558937
+ next = appendUnresolved(next, `Completion claim overstates verification: claimed all ${claimed} tests pass, but latest successful test evidence reported ${observed}.`, claim.id);
558938
+ }
558939
+ return next;
558940
+ }
558630
558941
  function finalizeCompletionLedgerTruth(ledger) {
558631
558942
  let unresolved = [...ledger.unresolved];
558632
- let lastMutation = -1;
558943
+ let lastVerificationInvalidatingMutation = -1;
558633
558944
  let lastVerification = -1;
558634
558945
  const lastSuccessfulVerificationByFamily = /* @__PURE__ */ new Map();
558635
558946
  ledger.evidence.forEach((entry, index) => {
558636
- if (isMutationEvidence(entry))
558637
- lastMutation = index;
558947
+ if (isVerificationInvalidatingMutation(entry))
558948
+ lastVerificationInvalidatingMutation = index;
558638
558949
  if (isSuccessfulVerificationEvidence(entry)) {
558639
558950
  lastVerification = index;
558640
558951
  const family = verificationFamily(entry);
@@ -558650,13 +558961,14 @@ function finalizeCompletionLedgerTruth(ledger) {
558650
558961
  unresolved = appendUnresolved(unresolved, `Verification failed or did not prove success: ${entry.summary}`, entry.id);
558651
558962
  }
558652
558963
  }
558653
- if (isStaleEditEvidence(entry)) {
558964
+ if (isStaleEditEvidence(entry) && !staleEditResolvedByLaterMutation(ledger.evidence, entry, index)) {
558654
558965
  unresolved = appendUnresolved(unresolved, `Stale edit failure remains unresolved: ${entry.summary}`, entry.id);
558655
558966
  }
558656
558967
  });
558657
- if (lastVerification >= 0 && lastMutation > lastVerification) {
558968
+ if (lastVerification >= 0 && lastVerificationInvalidatingMutation > lastVerification) {
558658
558969
  unresolved = appendUnresolved(unresolved, "File changes occurred after the last successful verification; final verification is stale.", "verification_freshness");
558659
558970
  }
558971
+ unresolved = appendTestCountOverclaims(ledger, unresolved);
558660
558972
  const status = ledger.status === "blocked" || ledger.status === "request_changes" ? ledger.status : unresolved.length > 0 ? "incomplete_verification" : ledger.status;
558661
558973
  return {
558662
558974
  ...ledger,
@@ -564299,12 +564611,12 @@ var init_reflectionBuffer = __esm({
564299
564611
  if (!this.persistPath)
564300
564612
  return;
564301
564613
  try {
564302
- const { writeFileSync: writeFileSync91, mkdirSync: mkdirSync106, existsSync: existsSync164 } = __require("node:fs");
564614
+ const { writeFileSync: writeFileSync90, mkdirSync: mkdirSync106, existsSync: existsSync164 } = __require("node:fs");
564303
564615
  const { join: join179 } = __require("node:path");
564304
564616
  const dir = join179(this.persistPath, "..");
564305
564617
  if (!existsSync164(dir))
564306
564618
  mkdirSync106(dir, { recursive: true });
564307
- writeFileSync91(this.persistPath, JSON.stringify(this.state, null, 2));
564619
+ writeFileSync90(this.persistPath, JSON.stringify(this.state, null, 2));
564308
564620
  } catch {
564309
564621
  }
564310
564622
  }
@@ -566961,7 +567273,6 @@ function normalizeFailurePatterns(patterns) {
566961
567273
  });
566962
567274
  }
566963
567275
  function buildFailureModeHandoff(input) {
566964
- const patterns = normalizeFailurePatterns(input.errorPatterns).slice(0, input.maxPatterns ?? 10);
566965
567276
  const toolCalls = input.toolCallLog ?? [];
566966
567277
  const maxRecentCalls = input.maxRecentCalls ?? 8;
566967
567278
  const recentCalls = maxRecentCalls > 0 ? toolCalls.slice(-maxRecentCalls) : [];
@@ -566973,6 +567284,7 @@ function buildFailureModeHandoff(input) {
566973
567284
  const currentStep = cleanInline(input.taskState?.currentStep, 180);
566974
567285
  const nextAction = cleanInline(input.taskState?.nextAction, 180);
566975
567286
  const goal = cleanInline(input.taskGoal || input.taskState?.goal || input.taskState?.originalGoal || "", 260);
567287
+ const patterns = normalizeFailurePatterns(input.errorPatterns).slice(0, input.maxPatterns ?? 10);
566976
567288
  if (patterns.length === 0 && recentCalls.length === 0 && modified.length === 0 && failedApproaches.length === 0 && !goal) {
566977
567289
  return null;
566978
567290
  }
@@ -567915,12 +568227,12 @@ var init_adversaryStream = __esm({
567915
568227
  if (!this.persistPath)
567916
568228
  return;
567917
568229
  try {
567918
- const { writeFileSync: writeFileSync91, mkdirSync: mkdirSync106, existsSync: existsSync164 } = __require("node:fs");
568230
+ const { writeFileSync: writeFileSync90, mkdirSync: mkdirSync106, existsSync: existsSync164 } = __require("node:fs");
567919
568231
  const { dirname: dirname54 } = __require("node:path");
567920
568232
  const dir = dirname54(this.persistPath);
567921
568233
  if (!existsSync164(dir))
567922
568234
  mkdirSync106(dir, { recursive: true });
567923
- writeFileSync91(this.persistPath, JSON.stringify({ ledger: this.ledger }, null, 2));
568235
+ writeFileSync90(this.persistPath, JSON.stringify({ ledger: this.ledger }, null, 2));
567924
568236
  } catch {
567925
568237
  }
567926
568238
  }
@@ -567942,6 +568254,10 @@ function resolutionSystemPrompt() {
567942
568254
  " - exit code 0 on an unrelated command does not resolve the request.",
567943
568255
  " - Doing PART of the request, or adjacent work, is NOT resolution.",
567944
568256
  " - If the request had multiple parts, EVERY part must be addressed.",
568257
+ " - If the original request explicitly allows a degraded fallback such as",
568258
+ " documenting/reporting a tool failure, that fallback can resolve the task",
568259
+ " only when the failure is clearly disclosed and the requested fallback",
568260
+ " artifact/report/verifier is evidenced.",
567945
568261
  "",
567946
568262
  "Respond with ONLY a JSON object, no prose, no code fences:",
567947
568263
  '{"resolved": true|false,',
@@ -567953,6 +568269,25 @@ function resolutionSystemPrompt() {
567953
568269
  "original request. When in doubt, resolved=false and name what is missing."
567954
568270
  ].join("\n");
567955
568271
  }
568272
+ function detectExplicitDegradedCompletion(i2) {
568273
+ const original = i2.originalGoal.toLowerCase();
568274
+ const summary = i2.proposedSummary.toLowerCase();
568275
+ const evidence = `${i2.actionsDigest}
568276
+ ${i2.evidenceDigest}`.toLowerCase();
568277
+ const permitsFallback = /\bif\b[\s\S]{0,160}\b(?:fails?|failed|failure|blocked|unavailable|cannot|can't|unable)\b[\s\S]{0,180}\b(?:document|report|note|record|summari[sz]e|explain)\b/.test(original) || /\b(?:document|report|note|record|summari[sz]e|explain)\b[\s\S]{0,180}\b(?:fails?|failed|failure|blocked|unavailable|cannot|can't|unable)\b/.test(original) || /\b(?:fallback|degraded|best effort|best-effort|honestly document|document honestly)\b/.test(original);
568278
+ if (!permitsFallback)
568279
+ return null;
568280
+ const disclosesFailure = /\b(?:fail(?:ed|ure)?|blocked|unable|could not|couldn't|cannot|can't|degraded|partial|not available|unavailable)\b/.test(summary);
568281
+ if (!disclosesFailure)
568282
+ return null;
568283
+ const hasFallbackEvidence = /\b(?:passed|success|succeeded|verified|wrote|created|saved|report|artifact|file changed|files changed|last test outcome: passed|exit code 0)\b/.test(evidence) || /\b(?:verifier|verification|report)\b/.test(summary);
568284
+ if (!hasFallbackEvidence)
568285
+ return null;
568286
+ return {
568287
+ accepted: true,
568288
+ reason: "original request explicitly allowed a degraded/failure-report fallback and the completion disclosed the failure with fallback evidence"
568289
+ };
568290
+ }
567956
568291
  function buildResolutionPrompt(i2) {
567957
568292
  return [
567958
568293
  "ORIGINAL REQUEST (what the user actually asked for):",
@@ -568003,6 +568338,134 @@ var init_completion_resolution_verifier = __esm({
568003
568338
  }
568004
568339
  });
568005
568340
 
568341
+ // packages/orchestrator/dist/todoTruth.js
568342
+ function normalizeText2(value2) {
568343
+ return value2.toLowerCase().replace(/[_-]+/g, " ").replace(/[^a-z0-9./ ]+/g, " ").replace(/\s+/g, " ").trim();
568344
+ }
568345
+ function extractArg(argsKey, key) {
568346
+ if (!argsKey)
568347
+ return "";
568348
+ const re = new RegExp(`(?:^|,)${key}=([^,]+)`);
568349
+ return argsKey.match(re)?.[1]?.trim() ?? "";
568350
+ }
568351
+ function toolAliases(toolName) {
568352
+ const normalized = normalizeText2(toolName);
568353
+ const spaceAlias = normalizeText2(toolName.replace(/_/g, " "));
568354
+ const compactAlias = toolName.toLowerCase().replace(/[^a-z0-9]+/g, "");
568355
+ return [...new Set([normalized, spaceAlias, compactAlias].filter(Boolean))];
568356
+ }
568357
+ function todoMentionsFailedCall(todo, call) {
568358
+ if (NON_WORK_TOOLS.has(call.name))
568359
+ return false;
568360
+ const content = normalizeText2(todo.content);
568361
+ const compactContent2 = todo.content.toLowerCase().replace(/[^a-z0-9]+/g, "");
568362
+ const mentionsTool = toolAliases(call.name).some((alias) => {
568363
+ if (alias.length <= 2)
568364
+ return false;
568365
+ if (/^[a-z0-9]+$/.test(alias) && alias === call.name.toLowerCase().replace(/[^a-z0-9]+/g, "")) {
568366
+ return compactContent2.includes(alias);
568367
+ }
568368
+ return content.includes(alias);
568369
+ });
568370
+ if (!mentionsTool)
568371
+ return false;
568372
+ const action = normalizeText2(extractArg(call.argsKey, "action"));
568373
+ if (!action)
568374
+ return true;
568375
+ return content.includes(action) || action.length <= 2;
568376
+ }
568377
+ function evidenceLine(call) {
568378
+ const preview = String(call.outputPreview ?? "").split("\n").map((line) => line.trim()).find(Boolean);
568379
+ const action = extractArg(call.argsKey, "action");
568380
+ const detail = preview || "tool returned failure";
568381
+ return `${call.name}${action ? ` ${action}` : ""}: ${detail}`.slice(0, 260);
568382
+ }
568383
+ function hasLaterSuccessForSameFamily(calls, failedIndex) {
568384
+ const failed = calls[failedIndex];
568385
+ if (!failed)
568386
+ return false;
568387
+ const failedAction = normalizeText2(extractArg(failed.argsKey, "action"));
568388
+ for (let i2 = failedIndex + 1; i2 < calls.length; i2++) {
568389
+ const next = calls[i2];
568390
+ if (!next || next.success !== true || next.name !== failed.name)
568391
+ continue;
568392
+ const nextAction = normalizeText2(extractArg(next.argsKey, "action"));
568393
+ if (!failedAction || !nextAction || failedAction === nextAction)
568394
+ return true;
568395
+ }
568396
+ return false;
568397
+ }
568398
+ function firstUnresolvedChild(todo, childrenByParent) {
568399
+ const id = todo.id;
568400
+ if (!id)
568401
+ return null;
568402
+ const children2 = childrenByParent.get(id) ?? [];
568403
+ return children2.find((child) => child.status === "blocked") ?? children2.find((child) => child.status === "in_progress") ?? children2.find((child) => child.status === "pending") ?? null;
568404
+ }
568405
+ function reconcileCompletedTodosWithEvidence(input) {
568406
+ const nextTodos = input.todos.map((todo) => ({ ...todo }));
568407
+ const downgrades = [];
568408
+ const childrenByParent = /* @__PURE__ */ new Map();
568409
+ for (const todo of nextTodos) {
568410
+ if (!todo.parentId)
568411
+ continue;
568412
+ const arr = childrenByParent.get(todo.parentId) ?? [];
568413
+ arr.push(todo);
568414
+ childrenByParent.set(todo.parentId, arr);
568415
+ }
568416
+ for (const todo of nextTodos) {
568417
+ if (todo.status !== "completed")
568418
+ continue;
568419
+ const unresolvedChild = firstUnresolvedChild(todo, childrenByParent);
568420
+ if (unresolvedChild) {
568421
+ const childStatus = unresolvedChild.status;
568422
+ const targetStatus = childStatus === "blocked" ? "blocked" : "in_progress";
568423
+ const blocker2 = childStatus === "blocked" ? `Child todo blocked: ${unresolvedChild.content}${unresolvedChild.blocker ? ` (${unresolvedChild.blocker})` : ""}` : `Child todo not complete: [${childStatus}] ${unresolvedChild.content}`;
568424
+ downgrades.push({
568425
+ id: todo.id,
568426
+ content: todo.content,
568427
+ from: "completed",
568428
+ to: targetStatus,
568429
+ blocker: blocker2,
568430
+ evidence: blocker2
568431
+ });
568432
+ todo.status = targetStatus;
568433
+ todo.blocker = blocker2;
568434
+ continue;
568435
+ }
568436
+ const startTurn = todo.id && input.todoStartTurnById?.has(todo.id) ? input.todoStartTurnById.get(todo.id) : 0;
568437
+ const relevantCalls = input.toolCallLog.filter((call) => (call.turn ?? 0) >= startTurn);
568438
+ const failedIndex = relevantCalls.findIndex((call) => call.success === false && todoMentionsFailedCall(todo, call) && !hasLaterSuccessForSameFamily(relevantCalls, relevantCalls.indexOf(call)));
568439
+ if (failedIndex < 0)
568440
+ continue;
568441
+ const failed = relevantCalls[failedIndex];
568442
+ const evidence = evidenceLine(failed);
568443
+ const blocker = `Completion claim contradicted by failed ${failed.name} evidence. Reconfigure this subtask: verify whether the goal is already satisfied, choose a different target/tool, or leave the todo blocked with the root cause.`;
568444
+ downgrades.push({
568445
+ id: todo.id,
568446
+ content: todo.content,
568447
+ from: "completed",
568448
+ to: "blocked",
568449
+ blocker,
568450
+ evidence
568451
+ });
568452
+ todo.status = "blocked";
568453
+ todo.blocker = `${blocker} Evidence: ${evidence}`;
568454
+ }
568455
+ return {
568456
+ todos: nextTodos,
568457
+ changed: downgrades.length > 0,
568458
+ downgrades
568459
+ };
568460
+ }
568461
+ var NON_WORK_TOOLS;
568462
+ var init_todoTruth = __esm({
568463
+ "packages/orchestrator/dist/todoTruth.js"() {
568464
+ "use strict";
568465
+ NON_WORK_TOOLS = /* @__PURE__ */ new Set(["todo_write", "todo_read", "task_complete"]);
568466
+ }
568467
+ });
568468
+
568006
568469
  // packages/orchestrator/dist/evidenceBranch.js
568007
568470
  function buildStructuralPreview2(lines, path12, query) {
568008
568471
  const n2 = lines.length;
@@ -570209,6 +570672,7 @@ var init_agenticRunner = __esm({
570209
570672
  init_evidenceLedger();
570210
570673
  init_adversaryStream();
570211
570674
  init_completion_resolution_verifier();
570675
+ init_todoTruth();
570212
570676
  init_evidenceBranch();
570213
570677
  init_resolution_memory();
570214
570678
  init_contextEngine();
@@ -570384,6 +570848,7 @@ var init_agenticRunner = __esm({
570384
570848
  // Research: Kumaran et al. (2016) — complementary learning systems
570385
570849
  // Fast learning from errors → immediate behavioral change
570386
570850
  _errorPatterns = /* @__PURE__ */ new Map();
570851
+ _taskRelevantErrorPatterns = /* @__PURE__ */ new Map();
570387
570852
  _errorGuidanceInjected = /* @__PURE__ */ new Set();
570388
570853
  // prevent duplicate injection per turn
570389
570854
  // REG-26 (Patch C): Reflexion-style structured failure memory. Indexed by
@@ -570961,6 +571426,145 @@ ${parts.join("\n")}
570961
571426
  writesUserTaskArtifacts() {
570962
571427
  return this.options.artifactMode === "user-task" && !this.options.subAgent;
570963
571428
  }
571429
+ _backendModelLabel(backend = this.backend) {
571430
+ const b = backend;
571431
+ const direct = b["model"] ?? b["resolvedModel"] ?? b["modelName"];
571432
+ if (typeof direct === "string" && direct.trim())
571433
+ return direct.trim();
571434
+ const nested = b["config"];
571435
+ if (nested && typeof nested === "object") {
571436
+ const model = nested["model"];
571437
+ if (typeof model === "string" && model.trim())
571438
+ return model.trim();
571439
+ }
571440
+ return "unknown";
571441
+ }
571442
+ _emitModelResolutionTelemetry(purpose, turn) {
571443
+ try {
571444
+ const backendName = this.backend.constructor?.name ?? "unknown";
571445
+ const resolved = this._backendModelLabel(this.backend);
571446
+ this.emit({
571447
+ type: "status",
571448
+ content: `Model resolution: purpose=${purpose} resolved=${resolved} backend=${backendName}`,
571449
+ turn,
571450
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
571451
+ });
571452
+ } catch {
571453
+ }
571454
+ }
571455
+ _cosineSimilarity(a2, b) {
571456
+ if (!a2 || !b || a2.length !== b.length || a2.length === 0)
571457
+ return 0;
571458
+ let dot = 0;
571459
+ let na = 0;
571460
+ let nb = 0;
571461
+ for (let i2 = 0; i2 < a2.length; i2++) {
571462
+ const av = a2[i2] ?? 0;
571463
+ const bv = b[i2] ?? 0;
571464
+ dot += av * bv;
571465
+ na += av * av;
571466
+ nb += bv * bv;
571467
+ }
571468
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
571469
+ return denom > 0 ? dot / denom : 0;
571470
+ }
571471
+ _embeddingBaseUrl() {
571472
+ const raw = this.backend["baseUrl"] || "http://localhost:11434";
571473
+ return raw.replace(/\/v1\/?$/, "");
571474
+ }
571475
+ _failurePatternText(pattern) {
571476
+ return [
571477
+ `signature: ${pattern.signature}`,
571478
+ pattern.tool ? `tool: ${pattern.tool}` : "",
571479
+ pattern.errorType ? `errorType: ${pattern.errorType}` : "",
571480
+ pattern.guidance ? `guidance: ${pattern.guidance}` : ""
571481
+ ].filter(Boolean).join("\n");
571482
+ }
571483
+ async _inferRelevantFailurePatternSignatures(taskGoal, candidates, maxPatterns) {
571484
+ if (candidates.length === 0)
571485
+ return /* @__PURE__ */ new Set();
571486
+ try {
571487
+ this._emitModelResolutionTelemetry("failure_pattern_relevance");
571488
+ const backend = this._auxInferenceBackend();
571489
+ const resp = await backend.chatCompletion({
571490
+ messages: [
571491
+ {
571492
+ role: "system",
571493
+ content: "You select prior failure patterns that are semantically relevant to the active task. Return only JSON."
571494
+ },
571495
+ {
571496
+ role: "user",
571497
+ content: JSON.stringify({
571498
+ taskGoal,
571499
+ max: maxPatterns,
571500
+ candidates: candidates.slice(0, 30).map((pattern) => ({
571501
+ signature: pattern.signature,
571502
+ tool: pattern.tool,
571503
+ errorType: pattern.errorType,
571504
+ guidance: pattern.guidance
571505
+ })),
571506
+ outputSchema: { relevant: ["signature"] }
571507
+ })
571508
+ }
571509
+ ],
571510
+ tools: [],
571511
+ temperature: 0,
571512
+ maxTokens: 500,
571513
+ timeoutMs: 2e4
571514
+ });
571515
+ const raw = resp.choices?.[0]?.message?.content ?? "";
571516
+ const start2 = raw.indexOf("{");
571517
+ const end = raw.lastIndexOf("}");
571518
+ if (start2 < 0 || end <= start2)
571519
+ return /* @__PURE__ */ new Set();
571520
+ const parsed = JSON.parse(raw.slice(start2, end + 1));
571521
+ return new Set((parsed.relevant ?? []).map((item) => String(item)).filter((signature) => candidates.some((pattern) => pattern.signature === signature)).slice(0, maxPatterns));
571522
+ } catch {
571523
+ return /* @__PURE__ */ new Set();
571524
+ }
571525
+ }
571526
+ async _selectTaskRelevantErrorPatterns(taskGoal, maxPatterns) {
571527
+ const candidates = normalizeFailurePatterns(this._errorPatterns).slice(0, 40);
571528
+ if (!taskGoal.trim() || candidates.length === 0)
571529
+ return /* @__PURE__ */ new Map();
571530
+ const selected = /* @__PURE__ */ new Map();
571531
+ let selectedBy = "none";
571532
+ try {
571533
+ const embeddings = await generateEmbeddingBatch([taskGoal, ...candidates.map((pattern) => this._failurePatternText(pattern))], { baseUrl: this._embeddingBaseUrl(), timeoutMs: 12e3 });
571534
+ const query = embeddings[0]?.vector;
571535
+ const minScore = Number.parseFloat(process.env["OMNIUS_FAILURE_PATTERN_SIM_MIN"] ?? "0.58");
571536
+ if (query) {
571537
+ const scored = candidates.map((pattern, index) => ({
571538
+ pattern,
571539
+ score: embeddings[index + 1]?.vector ? this._cosineSimilarity(query, embeddings[index + 1].vector) : 0
571540
+ })).filter((item) => item.score >= minScore).sort((a2, b) => b.score - a2.score).slice(0, maxPatterns);
571541
+ for (const item of scored) {
571542
+ const raw = this._errorPatterns.get(item.pattern.signature);
571543
+ if (raw)
571544
+ selected.set(item.pattern.signature, raw);
571545
+ }
571546
+ if (selected.size > 0)
571547
+ selectedBy = "vector";
571548
+ }
571549
+ } catch {
571550
+ }
571551
+ if (selected.size === 0) {
571552
+ const relevant = await this._inferRelevantFailurePatternSignatures(taskGoal, candidates, maxPatterns);
571553
+ for (const signature of relevant) {
571554
+ const raw = this._errorPatterns.get(signature);
571555
+ if (raw)
571556
+ selected.set(signature, raw);
571557
+ }
571558
+ if (selected.size > 0)
571559
+ selectedBy = "inference";
571560
+ }
571561
+ this.emit({
571562
+ type: "status",
571563
+ content: `Failure handoff semantic selection: ${selected.size}/${candidates.length} persisted pattern(s) selected by ${selectedBy}`,
571564
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
571565
+ });
571566
+ return selected;
571567
+ }
570964
571568
  _persistCompletionContract(contract) {
570965
571569
  if (!this.writesUserTaskArtifacts())
570966
571570
  return;
@@ -571961,12 +572565,14 @@ ${modelVisible}` : modelVisible || result.error || displayOutput || "";
571961
572565
  if (!this._completionLedger || input.toolName === "task_complete")
571962
572566
  return;
571963
572567
  const realFileMutation = input.realFileMutation ?? this._isRealProjectMutation(input.toolName, input.result);
571964
- const realMutationPaths = input.realMutationPaths ?? (realFileMutation ? this._extractToolTargetPaths(input.toolName, input.args, input.result) : []);
572568
+ const attemptedTargetPaths = this._extractToolTargetPaths(input.toolName, input.args, input.result);
572569
+ const realMutationPaths = input.realMutationPaths ?? (realFileMutation ? attemptedTargetPaths : []);
571965
572570
  this._completionLedger = recordToolEvidence(this._completionLedger, {
571966
572571
  name: input.toolName,
571967
572572
  success: input.result.success,
571968
572573
  outputPreview: (input.outputPreview ?? this._toolEvidencePreview(input.result)).toString().slice(0, 500),
571969
- argsKey: input.argsKey.slice(0, 300)
572574
+ argsKey: input.argsKey.slice(0, 300),
572575
+ targetPaths: attemptedTargetPaths
571970
572576
  });
571971
572577
  if (realFileMutation && realMutationPaths.length > 0) {
571972
572578
  for (const filePath of realMutationPaths) {
@@ -571975,7 +572581,8 @@ ${modelVisible}` : modelVisible || result.error || displayOutput || "";
571975
572581
  toolName: input.toolName,
571976
572582
  success: true,
571977
572583
  summary: `file change: ${filePath}`,
571978
- rawRef: `file://${filePath}`
572584
+ rawRef: `file://${filePath}`,
572585
+ targetPaths: [filePath]
571979
572586
  });
571980
572587
  }
571981
572588
  }
@@ -572323,7 +572930,7 @@ ${context2 ?? ""}`;
572323
572930
  `Task affect: uncertainty=${affect.uncertainty.toFixed(2)} frustration=${affect.frustration.toFixed(2)} confidence=${affect.confidence.toFixed(2)} momentum=${affect.momentum.toFixed(2)}`
572324
572931
  ].join("\n");
572325
572932
  }
572326
- _buildPreflightTaskMemoryRecall(taskGoal) {
572933
+ async _buildPreflightTaskMemoryRecall(taskGoal) {
572327
572934
  if (process.env["OMNIUS_DISABLE_PREFLIGHT_MEMORY_RECALL"] === "1")
572328
572935
  return "";
572329
572936
  if (this.options.stateDir || this.options.subAgent)
@@ -572332,17 +572939,31 @@ ${context2 ?? ""}`;
572332
572939
  return "";
572333
572940
  try {
572334
572941
  const query = taskGoal.slice(0, 1e3);
572335
- const results = this._episodeStore.search({ query, limit: 6 });
572336
- const useful = results.filter((entry) => typeof entry.content === "string" && entry.content.trim().length >= 30).slice(0, 4);
572942
+ const embedding = await generateEmbedding(query, {
572943
+ baseUrl: this._embeddingBaseUrl(),
572944
+ timeoutMs: 1e4
572945
+ });
572946
+ if (!embedding?.vector)
572947
+ return "";
572948
+ const results = this._episodeStore.search({ query, limit: 12 }, {
572949
+ queryEmbedding: embedding.vector,
572950
+ lexicalWeight: 0,
572951
+ embeddingWeight: 1
572952
+ });
572953
+ const minScore = Number.parseFloat(process.env["OMNIUS_PREFLIGHT_MEMORY_SIM_MIN"] ?? "0.58");
572954
+ const useful = results.map((entry) => ({
572955
+ entry,
572956
+ score: entry.embedding ? this._cosineSimilarity(embedding.vector, entry.embedding) : 0
572957
+ })).filter(({ entry, score }) => typeof entry.content === "string" && entry.content.trim().length >= 30 && score >= minScore).sort((a2, b) => b.score - a2.score).slice(0, 3);
572337
572958
  if (useful.length === 0)
572338
572959
  return "";
572339
- const lines = useful.map((entry, index) => {
572340
- const tool = typeof entry.metadata?.["toolName"] === "string" ? ` tool=${entry.metadata["toolName"]}` : "";
572341
- return `${index + 1}. ${entry.content.replace(/\s+/g, " ").slice(0, 260)}${tool}`;
572960
+ const lines = useful.map(({ entry, score }, index) => {
572961
+ const tool = typeof entry.toolName === "string" && entry.toolName ? ` tool=${entry.toolName}` : "";
572962
+ return `${index + 1}. sim=${score.toFixed(3)} ${entry.content.replace(/\s+/g, " ").slice(0, 260)}${tool}`;
572342
572963
  });
572343
572964
  return [
572344
572965
  `[PREFLIGHT MEMORY RECALL]`,
572345
- `Retrieved task-relevant prior episodes before the first action. Use these as hypotheses, not truth; verify against current files/UI.`,
572966
+ `Retrieved vector-similar prior episodes before the first action. Use these as hypotheses, not truth; verify against current files/UI.`,
572346
572967
  ...lines,
572347
572968
  `If the current task resembles one of these, prefer the remembered working verification path and avoid the remembered failure pattern.`
572348
572969
  ].join("\n");
@@ -572455,9 +573076,26 @@ ${shellLines.join("\n")}` : "Commands run: none"
572455
573076
  const failCount = toolCallLog.filter((e2) => e2.success === false).length;
572456
573077
  evidenceParts.push(`Failed tool calls this run: ${failCount}`);
572457
573078
  const evidenceDigest = evidenceParts.join("\n");
573079
+ const degraded = detectExplicitDegradedCompletion({
573080
+ originalGoal,
573081
+ actionsDigest,
573082
+ evidenceDigest,
573083
+ proposedSummary
573084
+ });
573085
+ if (degraded) {
573086
+ this._resolutionGateRejections = 0;
573087
+ this.emit({
573088
+ type: "status",
573089
+ content: `Resolution gate accepted explicit degraded completion: ${degraded.reason}`,
573090
+ turn,
573091
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
573092
+ });
573093
+ return { proceed: true };
573094
+ }
572458
573095
  let verdict = null;
572459
573096
  try {
572460
573097
  const backend = this._auxInferenceBackend();
573098
+ this._emitModelResolutionTelemetry("completion_resolution", turn);
572461
573099
  for (let attempt = 0; attempt < 2 && !verdict; attempt++) {
572462
573100
  const resp = await backend.chatCompletion({
572463
573101
  messages: [
@@ -575393,7 +576031,8 @@ Rewrite it now for ${ctx3.model}.`;
575393
576031
  run_in_background: ["background", "background_run", "async"],
575394
576032
  max_turns: ["maxTurns", "turns"],
575395
576033
  timeout_ms: ["timeoutMs", "timeout"],
575396
- path: ["file", "filepath", "file_path", "filename"],
576034
+ path: ["file", "image", "media", "filepath", "file_path", "filename"],
576035
+ image: ["path", "file", "media", "filepath", "file_path", "filename"],
575397
576036
  command: ["cmd", "shell_command"],
575398
576037
  query: ["prompt", "task", "message", "input", "text"]
575399
576038
  };
@@ -575889,6 +576528,7 @@ Respond with your assessment, then take action.`;
575889
576528
  this.pendingUserMessages.length = 0;
575890
576529
  const persistentTaskGoal = cleanForStorage(actualUserGoal || "") || cleanedTask;
575891
576530
  const userGoal = persistentTaskGoal.slice(0, 500);
576531
+ this._taskRelevantErrorPatterns = process.env["OMNIUS_DISABLE_FAILURE_HANDOFF"] === "1" ? /* @__PURE__ */ new Map() : await this._selectTaskRelevantErrorPatterns(persistentTaskGoal, 10);
575892
576532
  this._taskState = {
575893
576533
  goal: userGoal,
575894
576534
  originalGoal: userGoal,
@@ -575933,6 +576573,7 @@ Respond with your assessment, then take action.`;
575933
576573
  contextWindowSize: this.options.contextWindowSize ?? 0,
575934
576574
  verbose: false
575935
576575
  });
576576
+ this._emitModelResolutionTelemetry("main");
575936
576577
  this._hookManager.runSessionHook("session_start", this._sessionId);
575937
576578
  if (this.writesUserTaskArtifacts()) {
575938
576579
  this._initializeCompletionContract(task, context2, actualUserGoal);
@@ -576163,7 +576804,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
576163
576804
  ...missionCompletionContract ? [{ role: "system", content: missionCompletionContract }] : [],
576164
576805
  { role: "user", content: userContent }
576165
576806
  ];
576166
- const preflightMemoryRecall = this._buildPreflightTaskMemoryRecall(persistentTaskGoal);
576807
+ const preflightMemoryRecall = await this._buildPreflightTaskMemoryRecall(persistentTaskGoal);
576167
576808
  if (preflightMemoryRecall) {
576168
576809
  messages2.splice(messages2.length - 1, 0, {
576169
576810
  role: "system",
@@ -576283,7 +576924,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
576283
576924
  try {
576284
576925
  const failureHandoff = buildFailureModeHandoff({
576285
576926
  taskGoal: persistentTaskGoal,
576286
- errorPatterns: this._errorPatterns,
576927
+ errorPatterns: this._taskRelevantErrorPatterns,
576287
576928
  toolCallLog,
576288
576929
  taskState: this._taskState,
576289
576930
  maxPatterns: 10,
@@ -579760,13 +580401,15 @@ Respond with EXACTLY this structure before your next tool call:
579760
580401
  default:
579761
580402
  guidance = `This tool failed previously with a similar error. Review the error message carefully and adjust your approach before retrying.`;
579762
580403
  }
579763
- this._errorPatterns.set(sig, {
580404
+ const learnedPattern = {
579764
580405
  count,
579765
580406
  guidance,
579766
580407
  lastSeen: Date.now(),
579767
580408
  tool: tc.name,
579768
580409
  errorType
579769
- });
580410
+ };
580411
+ this._errorPatterns.set(sig, learnedPattern);
580412
+ this._taskRelevantErrorPatterns.set(sig, learnedPattern);
579770
580413
  if (this._failureStore) {
579771
580414
  try {
579772
580415
  this._failureStore.insert({
@@ -579816,13 +580459,48 @@ Respond with EXACTLY this structure before your next tool call:
579816
580459
  }
579817
580460
  if (tc.name === "todo_write") {
579818
580461
  try {
579819
- const _todosNow = this.readSessionTodos() || [];
580462
+ let _todosNow = this.readSessionTodos() || [];
579820
580463
  for (const _tp of _todosNow) {
579821
580464
  const _tpId = _tp.id;
579822
580465
  if (_tp.status === "in_progress" && _tpId && !this._todoInProgressTurn.has(_tpId)) {
579823
580466
  this._todoInProgressTurn.set(_tpId, turn);
579824
580467
  }
579825
580468
  }
580469
+ const truth = reconcileCompletedTodosWithEvidence({
580470
+ todos: _todosNow,
580471
+ toolCallLog,
580472
+ todoStartTurnById: this._todoInProgressTurn
580473
+ });
580474
+ if (truth.changed) {
580475
+ const sid = this._sessionId || process.env["OMNIUS_SESSION_ID"] || "default";
580476
+ writeTodos(sid, truth.todos.map((t2) => ({
580477
+ id: t2.id,
580478
+ content: t2.content,
580479
+ status: t2.status,
580480
+ parentId: t2.parentId,
580481
+ blocker: t2.blocker,
580482
+ verifyCommand: t2.verifyCommand,
580483
+ declaredArtifacts: t2.declaredArtifacts
580484
+ })));
580485
+ _todosNow = this.readSessionTodos() || truth.todos;
580486
+ const downgradeLines = truth.downgrades.slice(0, 6).map((d2) => `- ${d2.content}: ${d2.from} -> ${d2.to}; ${d2.evidence}`).join("\n");
580487
+ messages2.push({
580488
+ role: "system",
580489
+ content: [
580490
+ `[TODO TRUTH RECONCILIATION]`,
580491
+ `One or more completed todo claims contradicted the evidence or nested child status and were rewritten in the active todo list.`,
580492
+ downgradeLines,
580493
+ ``,
580494
+ `Reconfigure the affected subtask tree now: read/verify the current state, choose a different tool or target if the prior route failed, or leave the child todo blocked with the root cause. Do not mark a parent completed until every child is completed with evidence.`
580495
+ ].join("\n")
580496
+ });
580497
+ this.emit({
580498
+ type: "status",
580499
+ content: `Todo truth reconciled ${truth.downgrades.length} completed claim(s) from evidence/nested child state`,
580500
+ turn,
580501
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
580502
+ });
580503
+ }
579826
580504
  if (!this._newFieldNudgeFired) {
579827
580505
  this._todoWritesObservedForNudge++;
579828
580506
  const _anyFieldUsed = _todosNow.some((t2) => typeof t2.verifyCommand === "string" || Array.isArray(t2.declaredArtifacts));
@@ -580562,7 +581240,7 @@ Then use file_read on individual FILES inside it.`);
580562
581240
  if (process.env["OMNIUS_DISABLE_FAILURE_HANDOFF"] !== "1" && !result.success && turn - lastFailureHandoffTurn >= 4) {
580563
581241
  const runtimeHandoff = buildFailureModeHandoff({
580564
581242
  taskGoal: persistentTaskGoal,
580565
- errorPatterns: this._errorPatterns,
581243
+ errorPatterns: this._taskRelevantErrorPatterns,
580566
581244
  toolCallLog,
580567
581245
  taskState: this._taskState,
580568
581246
  maxPatterns: 5,
@@ -582419,7 +583097,7 @@ ${caveat}` : caveat;
582419
583097
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
582420
583098
  });
582421
583099
  try {
582422
- const { mkdirSync: mkdirSync106, readdirSync: readdirSync59, statSync: statSync59, unlinkSync: unlinkSync36, writeFileSync: writeFileSync91 } = __require("node:fs");
583100
+ const { mkdirSync: mkdirSync106, readdirSync: readdirSync59, statSync: statSync59, unlinkSync: unlinkSync36, writeFileSync: writeFileSync90 } = __require("node:fs");
582423
583101
  const { join: join179 } = __require("node:path");
582424
583102
  const contextDir = join179(this._workingDirectory || process.cwd(), ".omnius", "context");
582425
583103
  mkdirSync106(contextDir, { recursive: true });
@@ -582466,14 +583144,14 @@ ${caveat}` : caveat;
582466
583144
  mkdirSync106(kgSummaryDir, { recursive: true });
582467
583145
  const summaryFilename = `kg-summary-${this._sessionId}.md`;
582468
583146
  const outPath = join179(kgSummaryDir, summaryFilename);
582469
- writeFileSync91(outPath, lines.join("\n"), "utf-8");
582470
- writeFileSync91(join179(kgSummaryDir, "latest.md"), lines.join("\n"), "utf-8");
582471
- writeFileSync91(join179(contextDir, `kg-summary-latest.md`), [
583147
+ writeFileSync90(outPath, lines.join("\n"), "utf-8");
583148
+ writeFileSync90(join179(kgSummaryDir, "latest.md"), lines.join("\n"), "utf-8");
583149
+ writeFileSync90(join179(contextDir, `kg-summary-latest.md`), [
582472
583150
  "Latest KG summary moved to `.omnius/context/kg-summary/latest.md`.",
582473
583151
  "",
582474
583152
  lines.join("\n")
582475
583153
  ].join("\n"), "utf-8");
582476
- writeFileSync91(join179(kgSummaryDir, "index.json"), JSON.stringify({
583154
+ writeFileSync90(join179(kgSummaryDir, "index.json"), JSON.stringify({
582477
583155
  schema: "omnius.kg-summary-index.v1",
582478
583156
  latest: "latest.md",
582479
583157
  latestSessionFile: summaryFilename,
@@ -583310,7 +583988,7 @@ Actions: (1) list_directory on the parent directory to see what's there, (2) Che
583310
583988
  if (!this._workingDirectory)
583311
583989
  return;
583312
583990
  try {
583313
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = __require("node:fs");
583991
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = __require("node:fs");
583314
583992
  const { join: join179 } = __require("node:path");
583315
583993
  const sessionDir2 = this.options.stateDir ? join179(this.omniusStateDir(), "session", this._sessionId) : join179(this._workingDirectory, ".omnius", "session", this._sessionId);
583316
583994
  mkdirSync106(sessionDir2, { recursive: true });
@@ -583325,7 +584003,7 @@ Actions: (1) list_directory on the parent directory to see what's there, (2) Che
583325
584003
  memexEntryCount: this._memexArchive.size,
583326
584004
  fileRegistrySize: this._fileRegistry.size
583327
584005
  };
583328
- writeFileSync91(join179(sessionDir2, "checkpoint.json"), JSON.stringify(checkpoint, null, 2));
584006
+ writeFileSync90(join179(sessionDir2, "checkpoint.json"), JSON.stringify(checkpoint, null, 2));
583329
584007
  } catch {
583330
584008
  }
583331
584009
  }
@@ -583462,7 +584140,7 @@ ${tail}`;
583462
584140
  const analysis = await this.analyzeImageDataForContext(image.mime, image.base64, image.textWithoutImage.slice(0, 2e3));
583463
584141
  const imageNote = analysis.contextBlock ? `${analysis.contextBlock}
583464
584142
 
583465
- Use this image analysis. Do not repeat ${toolName} with the same arguments unless the scene has changed.` : `[Embedded image data omitted from model context; ${analysis.errorReason || "vision and OCR returned no text"}. Use any saved image path above with vision/image_read if further inspection is needed.]`;
584143
+ Use this image analysis. Do not repeat ${toolName} with the same arguments unless the scene has changed.` : `[Embedded image data omitted from model context; ${analysis.errorReason || "vision and OCR returned no text"}. If further inspection is needed, call vision first with the saved image path (Moondream caption/query), then use image_read only for metadata/OCR/raw image ingress.]`;
583466
584144
  return {
583467
584145
  ...result,
583468
584146
  llmContent: `${image.textWithoutImage.trim()}
@@ -583498,7 +584176,7 @@ ${imageNote}`.trim()
583498
584176
  const imageUrl = `data:${mime};base64,${base642}`;
583499
584177
  const tmpImgPath = this.writeTempImageForOcr(mime, base642);
583500
584178
  const [visionOutcome, ocrOutcome] = await Promise.allSettled([
583501
- this.describeImageViaVisionSubagent(imageUrl, textContent),
584179
+ tmpImgPath ? this.describeImageViaPrimaryVision(tmpImgPath, imageUrl, textContent) : this.describeImageViaVisionSubagent(imageUrl, textContent),
583502
584180
  tmpImgPath ? this.extractImageOcrText(tmpImgPath) : Promise.resolve("")
583503
584181
  ]);
583504
584182
  const visionDesc = visionOutcome.status === "fulfilled" ? visionOutcome.value.trim() : "";
@@ -583513,6 +584191,26 @@ ${imageNote}`.trim()
583513
584191
  const errorReason = visionOutcome.status === "rejected" ? String(visionOutcome.reason?.message ?? visionOutcome.reason) : void 0;
583514
584192
  return { contextBlock: "", errorReason };
583515
584193
  }
584194
+ async describeImageViaPrimaryVision(imagePath, imageUrl, textContent) {
584195
+ try {
584196
+ const vision = new VisionTool(this._workingDirectory || process.cwd());
584197
+ const prompt = textContent ? `User context: ${textContent}
584198
+
584199
+ Describe the image in detail. Identify people, objects, text, UI elements, diagrams, errors, and any task-relevant visual metadata.` : "Describe the image in detail. Identify people, objects, text, UI elements, diagrams, errors, and any task-relevant visual metadata.";
584200
+ const result = await vision.execute({
584201
+ image: imagePath,
584202
+ action: "query",
584203
+ prompt,
584204
+ length: "long"
584205
+ });
584206
+ const output = String(result.llmContent || result.output || "").trim();
584207
+ if (result.success && output.length > 20)
584208
+ return output;
584209
+ throw new Error(result.error || "vision tool returned no description");
584210
+ } catch {
584211
+ return await this.describeImageViaVisionSubagent(imageUrl, textContent);
584212
+ }
584213
+ }
583516
584214
  async describeImageViaVisionSubagent(imageUrl, textContent) {
583517
584215
  const visionMessages = [
583518
584216
  {
@@ -584066,7 +584764,7 @@ ${content.slice(0, 8e3)}
584066
584764
  try {
584067
584765
  const compactFailureHandoff = buildFailureModeHandoff({
584068
584766
  taskGoal: this._taskState.goal,
584069
- errorPatterns: this._errorPatterns,
584767
+ errorPatterns: this._taskRelevantErrorPatterns,
584070
584768
  taskState: this._taskState,
584071
584769
  maxPatterns: 6,
584072
584770
  maxRecentCalls: 0
@@ -585985,12 +586683,12 @@ ${result}`
585985
586683
  let resizedBase64 = null;
585986
586684
  try {
585987
586685
  const { execSync: execSync63 } = await import("node:child_process");
585988
- const { writeFileSync: writeFileSync91, readFileSync: readFileSync133, unlinkSync: unlinkSync36 } = await import("node:fs");
586686
+ const { writeFileSync: writeFileSync90, readFileSync: readFileSync133, unlinkSync: unlinkSync36 } = await import("node:fs");
585989
586687
  const { join: join179 } = await import("node:path");
585990
586688
  const { tmpdir: tmpdir24 } = await import("node:os");
585991
586689
  const tmpIn = join179(tmpdir24(), `omnius_img_in_${Date.now()}.png`);
585992
586690
  const tmpOut = join179(tmpdir24(), `omnius_img_out_${Date.now()}.jpg`);
585993
- writeFileSync91(tmpIn, buffer2);
586691
+ writeFileSync90(tmpIn, buffer2);
585994
586692
  const pyBin = process.platform === "win32" ? "python" : "python3";
585995
586693
  const escapedIn = tmpIn.replace(/\\/g, "\\\\");
585996
586694
  const escapedOut = tmpOut.replace(/\\/g, "\\\\");
@@ -594971,10 +595669,10 @@ transcribe-cli error: ${transcribeCliError}` : "";
594971
595669
  wordTimestamps: false
594972
595670
  });
594973
595671
  if (outputDir2) {
594974
- const { basename: basename39 } = await import("node:path");
595672
+ const { basename: basename40 } = await import("node:path");
594975
595673
  const transcriptDir = join120(outputDir2, ".omnius", "transcripts");
594976
595674
  mkdirSync63(transcriptDir, { recursive: true });
594977
- const outFile = join120(transcriptDir, `${basename39(filePath)}.txt`);
595675
+ const outFile = join120(transcriptDir, `${basename40(filePath)}.txt`);
594978
595676
  writeFileSync53(outFile, result.text, "utf-8");
594979
595677
  }
594980
595678
  return {
@@ -594990,10 +595688,10 @@ transcribe-cli error: ${transcribeCliError}` : "";
594990
595688
  const fb = await transcribeFileViaWhisper(filePath, this.config.model);
594991
595689
  if (fb) {
594992
595690
  if (outputDir2) {
594993
- const { basename: basename39 } = await import("node:path");
595691
+ const { basename: basename40 } = await import("node:path");
594994
595692
  const transcriptDir = join120(outputDir2, ".omnius", "transcripts");
594995
595693
  mkdirSync63(transcriptDir, { recursive: true });
594996
- const outFile = join120(transcriptDir, `${basename39(filePath)}.txt`);
595694
+ const outFile = join120(transcriptDir, `${basename40(filePath)}.txt`);
594997
595695
  writeFileSync53(outFile, fb.text, "utf-8");
594998
595696
  }
594999
595697
  return fb;
@@ -612842,6 +613540,33 @@ function buildTodoProgressBar(todos, maxWidth) {
612842
613540
  if (truncated && maxWidth > 0) out += `${DIM_LABEL}…${RESET3}`;
612843
613541
  return out;
612844
613542
  }
613543
+ function orderTodosForDisplay(todos) {
613544
+ const byParent = /* @__PURE__ */ new Map();
613545
+ const byId = new Set(todos.map((t2) => t2.id));
613546
+ const roots = [];
613547
+ for (const todo of todos) {
613548
+ if (todo.parentId && byId.has(todo.parentId)) {
613549
+ const arr = byParent.get(todo.parentId) ?? [];
613550
+ arr.push(todo);
613551
+ byParent.set(todo.parentId, arr);
613552
+ } else {
613553
+ roots.push(todo);
613554
+ }
613555
+ }
613556
+ const out = [];
613557
+ const seen = /* @__PURE__ */ new Set();
613558
+ const visit = (todo, depth) => {
613559
+ if (seen.has(todo.id)) return;
613560
+ seen.add(todo.id);
613561
+ out.push({ ...todo, depth: Math.min(depth, 4) });
613562
+ for (const child of byParent.get(todo.id) ?? []) {
613563
+ visit(child, depth + 1);
613564
+ }
613565
+ };
613566
+ for (const root of roots) visit(root, 0);
613567
+ for (const todo of todos) visit(todo, 0);
613568
+ return out;
613569
+ }
612845
613570
  function render() {
612846
613571
  if (!_enabled) return;
612847
613572
  if (!panelEffectivelyVisible()) {
@@ -612867,16 +613592,18 @@ function render() {
612867
613592
  const progressBar = buildTodoProgressBar(_lastTodos, maxBarWidth);
612868
613593
  const headerText = `${headerPrefix}${progressBar}`;
612869
613594
  lines.push(headerText);
612870
- const visible = _lastTodos.slice(0, MAX_VISIBLE_ROWS - 1);
613595
+ const displayTodos = orderTodosForDisplay(_lastTodos);
613596
+ const visible = displayTodos.slice(0, MAX_VISIBLE_ROWS - 1);
612871
613597
  for (const t2 of visible) {
612872
613598
  const { mark, color } = statusToAnsi(t2.status);
612873
613599
  const contentWidth = Math.max(4, cols - 8);
612874
- const contentText = t2.content + (t2.blocker ? ` (blocked: ${t2.blocker})` : "");
613600
+ const indent2 = t2.depth > 0 ? `${" ".repeat(t2.depth - 1)}- ` : "";
613601
+ const contentText = indent2 + t2.content + (t2.blocker ? ` (blocked: ${t2.blocker})` : "");
612875
613602
  const truncated = truncate2(contentText, contentWidth);
612876
613603
  lines.push(`${color}${mark}${RESET3} ${color}${truncated}${RESET3}`);
612877
613604
  }
612878
- if (_lastTodos.length > visible.length) {
612879
- const more = _lastTodos.length - visible.length;
613605
+ if (displayTodos.length > visible.length) {
613606
+ const more = displayTodos.length - visible.length;
612880
613607
  lines[lines.length - 1] = `${DIM_LABEL}… +${more} more${RESET3}`;
612881
613608
  }
612882
613609
  let out = HIDE + SAVE;
@@ -635942,13 +636669,13 @@ async function handleSlashCommand(input, ctx3) {
635942
636669
  try {
635943
636670
  const { randomBytes: randomBytes30 } = await import("node:crypto");
635944
636671
  const { homedir: homedir62 } = await import("node:os");
635945
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = await import("node:fs");
636672
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = await import("node:fs");
635946
636673
  const { join: join179 } = await import("node:path");
635947
636674
  const newKey = randomBytes30(16).toString("hex");
635948
636675
  process.env["OMNIUS_API_KEY"] = newKey;
635949
636676
  const dir = join179(homedir62(), ".omnius");
635950
636677
  mkdirSync106(dir, { recursive: true });
635951
- writeFileSync91(join179(dir, "api.key"), newKey + "\n", "utf8");
636678
+ writeFileSync90(join179(dir, "api.key"), newKey + "\n", "utf8");
635952
636679
  renderInfo(`New API key: ${c3.bold(c3.yellow(newKey))}`);
635953
636680
  renderInfo(
635954
636681
  "Restart the daemon to apply if needed. Use /access any to restart quickly."
@@ -636219,11 +636946,11 @@ async function handleSlashCommand(input, ctx3) {
636219
636946
  );
636220
636947
  try {
636221
636948
  const { homedir: homedir63 } = await import("node:os");
636222
- const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync92 } = await import("node:fs");
636949
+ const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync91 } = await import("node:fs");
636223
636950
  const { join: join180 } = await import("node:path");
636224
636951
  const dir = join180(homedir63(), ".omnius");
636225
636952
  mkdirSync107(dir, { recursive: true });
636226
- writeFileSync92(join180(dir, "api.key"), apiKey + "\n", "utf8");
636953
+ writeFileSync91(join180(dir, "api.key"), apiKey + "\n", "utf8");
636227
636954
  } catch {
636228
636955
  }
636229
636956
  }
@@ -636235,11 +636962,11 @@ async function handleSlashCommand(input, ctx3) {
636235
636962
  const port2 = parseInt(process.env["OMNIUS_PORT"] || "11435", 10);
636236
636963
  try {
636237
636964
  const { homedir: homedir63 } = await import("node:os");
636238
- const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync92 } = await import("node:fs");
636965
+ const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync91 } = await import("node:fs");
636239
636966
  const { join: join180 } = await import("node:path");
636240
636967
  const dir = join180(homedir63(), ".omnius");
636241
636968
  mkdirSync107(dir, { recursive: true });
636242
- writeFileSync92(join180(dir, "access"), `${val2}
636969
+ writeFileSync91(join180(dir, "access"), `${val2}
636243
636970
  `, "utf8");
636244
636971
  } catch {
636245
636972
  }
@@ -636339,11 +637066,11 @@ async function handleSlashCommand(input, ctx3) {
636339
637066
  );
636340
637067
  try {
636341
637068
  const { homedir: homedir63 } = await import("node:os");
636342
- const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync92 } = await import("node:fs");
637069
+ const { mkdirSync: mkdirSync107, writeFileSync: writeFileSync91 } = await import("node:fs");
636343
637070
  const { join: join180 } = await import("node:path");
636344
637071
  const dir = join180(homedir63(), ".omnius");
636345
637072
  mkdirSync107(dir, { recursive: true });
636346
- writeFileSync92(join180(dir, "api.key"), apiKey + "\n", "utf8");
637073
+ writeFileSync91(join180(dir, "api.key"), apiKey + "\n", "utf8");
636347
637074
  } catch {
636348
637075
  }
636349
637076
  }
@@ -636354,12 +637081,12 @@ async function handleSlashCommand(input, ctx3) {
636354
637081
  }
636355
637082
  const port = parseInt(process.env["OMNIUS_PORT"] || "11435", 10);
636356
637083
  const { homedir: homedir62 } = await import("node:os");
636357
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = await import("node:fs");
637084
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = await import("node:fs");
636358
637085
  const { join: join179 } = await import("node:path");
636359
637086
  try {
636360
637087
  const dir = join179(homedir62(), ".omnius");
636361
637088
  mkdirSync106(dir, { recursive: true });
636362
- writeFileSync91(join179(dir, "access"), `${val}
637089
+ writeFileSync90(join179(dir, "access"), `${val}
636363
637090
  `, "utf8");
636364
637091
  } catch (e2) {
636365
637092
  renderWarning(
@@ -645195,14 +645922,14 @@ async function handleVoiceMenu(ctx3, save3, hasLocal) {
645195
645922
  if (!jsonDrop.confirmed || !jsonDrop.path) {
645196
645923
  continue;
645197
645924
  }
645198
- const { basename: basename39, join: pathJoin } = await import("node:path");
645925
+ const { basename: basename40, join: pathJoin } = await import("node:path");
645199
645926
  const {
645200
645927
  copyFileSync: copyFileSync8,
645201
645928
  mkdirSync: mkdirSync106,
645202
645929
  existsSync: exists2
645203
645930
  } = await import("node:fs");
645204
645931
  const { homedir: homedir62 } = await import("node:os");
645205
- const modelName = basename39(onnxDrop.path, ".onnx").replace(
645932
+ const modelName = basename40(onnxDrop.path, ".onnx").replace(
645206
645933
  /[^a-zA-Z0-9_-]/g,
645207
645934
  "-"
645208
645935
  );
@@ -645722,7 +646449,7 @@ async function handleVoiceList(ctx3, focusFilename) {
645722
646449
  copyFileSync: cpf,
645723
646450
  mkdirSync: mkd
645724
646451
  } = __require("node:fs");
645725
- const { basename: basename39, join: pjoin } = __require("node:path");
646452
+ const { basename: basename40, join: pjoin } = __require("node:path");
645726
646453
  if (!fe(src2)) {
645727
646454
  renderError(`File not found: ${src2}`);
645728
646455
  helpers.render();
@@ -645735,7 +646462,7 @@ async function handleVoiceList(ctx3, focusFilename) {
645735
646462
  "clone-refs"
645736
646463
  );
645737
646464
  mkd(refsDir, { recursive: true });
645738
- const destName = basename39(src2);
646465
+ const destName = basename40(src2);
645739
646466
  const dest = pjoin(refsDir, destName);
645740
646467
  cpf(src2, dest);
645741
646468
  renderInfo(`Imported "${destName}" → ${dest}`);
@@ -646992,13 +647719,13 @@ async function handleSponsoredEndpoint(ctx3, local) {
646992
647719
  sponsors.push(...verified);
646993
647720
  if (verified.length > 0) {
646994
647721
  try {
646995
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = __require("node:fs");
647722
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = __require("node:fs");
646996
647723
  mkdirSync106(sponsorDir2, { recursive: true });
646997
647724
  const cached = verified.map((s2) => ({
646998
647725
  ...s2,
646999
647726
  lastVerified: Date.now()
647000
647727
  }));
647001
- writeFileSync91(knownFile, JSON.stringify(cached, null, 2));
647728
+ writeFileSync90(knownFile, JSON.stringify(cached, null, 2));
647002
647729
  } catch {
647003
647730
  }
647004
647731
  }
@@ -647202,7 +647929,7 @@ async function handlePeerEndpoint(peerId, authKey, ctx3, local, advertisedModels
647202
647929
  }
647203
647930
  if (models.length > 0) {
647204
647931
  try {
647205
- const { writeFileSync: writeFileSync91, mkdirSync: mkdirSync106 } = await import("node:fs");
647932
+ const { writeFileSync: writeFileSync90, mkdirSync: mkdirSync106 } = await import("node:fs");
647206
647933
  const { join: join179, dirname: dirname54 } = await import("node:path");
647207
647934
  const cachePath2 = join179(
647208
647935
  ctx3.repoRoot || process.cwd(),
@@ -647211,7 +647938,7 @@ async function handlePeerEndpoint(peerId, authKey, ctx3, local, advertisedModels
647211
647938
  "peer-models-cache.json"
647212
647939
  );
647213
647940
  mkdirSync106(dirname54(cachePath2), { recursive: true });
647214
- writeFileSync91(
647941
+ writeFileSync90(
647215
647942
  cachePath2,
647216
647943
  JSON.stringify(
647217
647944
  {
@@ -647285,7 +648012,7 @@ async function handlePeerEndpoint(peerId, authKey, ctx3, local, advertisedModels
647285
648012
  "Live model probe failed; using sponsor directory model advertisement."
647286
648013
  );
647287
648014
  try {
647288
- const { writeFileSync: writeFileSync91, mkdirSync: mkdirSync106 } = await import("node:fs");
648015
+ const { writeFileSync: writeFileSync90, mkdirSync: mkdirSync106 } = await import("node:fs");
647289
648016
  const { join: join179, dirname: dirname54 } = await import("node:path");
647290
648017
  const cachePath2 = join179(
647291
648018
  ctx3.repoRoot || process.cwd(),
@@ -647294,7 +648021,7 @@ async function handlePeerEndpoint(peerId, authKey, ctx3, local, advertisedModels
647294
648021
  "peer-models-cache.json"
647295
648022
  );
647296
648023
  mkdirSync106(dirname54(cachePath2), { recursive: true });
647297
- writeFileSync91(
648024
+ writeFileSync90(
647298
648025
  cachePath2,
647299
648026
  JSON.stringify(
647300
648027
  {
@@ -649992,13 +650719,13 @@ var init_commands = __esm({
649992
650719
  try {
649993
650720
  const { randomBytes: randomBytes30 } = await import("node:crypto");
649994
650721
  const { homedir: homedir62 } = await import("node:os");
649995
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = await import("node:fs");
650722
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = await import("node:fs");
649996
650723
  const { join: join179 } = await import("node:path");
649997
650724
  const apiKey = randomBytes30(16).toString("hex");
649998
650725
  process.env["OMNIUS_API_KEY"] = apiKey;
649999
650726
  const dir = join179(homedir62(), ".omnius");
650000
650727
  mkdirSync106(dir, { recursive: true });
650001
- writeFileSync91(join179(dir, "api.key"), apiKey + "\n", "utf8");
650728
+ writeFileSync90(join179(dir, "api.key"), apiKey + "\n", "utf8");
650002
650729
  renderInfo(`Generated API key: ${c3.bold(c3.yellow(apiKey))}`);
650003
650730
  renderInfo(
650004
650731
  "Use Authorization: Bearer <key> or click 'key' in the Web UI header to paste it."
@@ -650017,11 +650744,11 @@ var init_commands = __esm({
650017
650744
  const port = parseInt(process.env["OMNIUS_PORT"] || "11435", 10);
650018
650745
  try {
650019
650746
  const { homedir: homedir62 } = await import("node:os");
650020
- const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync91 } = await import("node:fs");
650747
+ const { mkdirSync: mkdirSync106, writeFileSync: writeFileSync90 } = await import("node:fs");
650021
650748
  const { join: join179 } = await import("node:path");
650022
650749
  const dir = join179(homedir62(), ".omnius");
650023
650750
  mkdirSync106(dir, { recursive: true });
650024
- writeFileSync91(join179(dir, "access"), `${val}
650751
+ writeFileSync90(join179(dir, "access"), `${val}
650025
650752
  `, "utf8");
650026
650753
  } catch {
650027
650754
  }
@@ -661171,6 +661898,175 @@ var init_soul_observations = __esm({
661171
661898
  }
661172
661899
  });
661173
661900
 
661901
+ // packages/cli/src/tui/visual-object-association.ts
661902
+ var visual_object_association_exports = {};
661903
+ __export(visual_object_association_exports, {
661904
+ associateVisualObjectFromImage: () => associateVisualObjectFromImage,
661905
+ extractExplicitVisualObjectLabels: () => extractExplicitVisualObjectLabels,
661906
+ formatVisualObjectMemoryContext: () => formatVisualObjectMemoryContext
661907
+ });
661908
+ import { basename as basename35 } from "node:path";
661909
+ function stringValue2(value2) {
661910
+ return typeof value2 === "string" ? value2.trim() : "";
661911
+ }
661912
+ function stringList(value2) {
661913
+ if (Array.isArray(value2)) {
661914
+ return value2.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
661915
+ }
661916
+ const single = stringValue2(value2);
661917
+ return single ? [single] : [];
661918
+ }
661919
+ function splitLabels(value2) {
661920
+ return value2.split(/[;,]/).map((part) => part.trim()).filter(Boolean).slice(0, 12);
661921
+ }
661922
+ function normalizeLabel(value2) {
661923
+ return value2.replace(/\s+/g, " ").replace(/^["'`]+|["'`]+$/g, "").trim();
661924
+ }
661925
+ function uniqueLabels(values) {
661926
+ const seen = /* @__PURE__ */ new Set();
661927
+ const out = [];
661928
+ for (const raw of values) {
661929
+ const label = normalizeLabel(raw);
661930
+ const key = label.toLowerCase();
661931
+ if (!label || seen.has(key)) continue;
661932
+ seen.add(key);
661933
+ out.push(label);
661934
+ }
661935
+ return out;
661936
+ }
661937
+ function explicitCaptionLabels(caption) {
661938
+ const text2 = stringValue2(caption);
661939
+ if (!text2) return [];
661940
+ const match = text2.match(/^\s*(?:object[_ -]?label|object|label|labels)\s*:\s*(.+)$/i);
661941
+ return match?.[1] ? splitLabels(match[1]) : [];
661942
+ }
661943
+ function identityNames(payload) {
661944
+ const names = /* @__PURE__ */ new Set();
661945
+ const add3 = (value2) => {
661946
+ const name10 = normalizeLabel(String(value2 ?? ""));
661947
+ if (name10) names.add(name10.toLowerCase());
661948
+ };
661949
+ for (const key of ["person", "person_name", "personName", "face_name", "faceName"]) {
661950
+ add3(payload[key]);
661951
+ }
661952
+ for (const person of stringList(payload["people"])) add3(person);
661953
+ const assertions = Array.isArray(payload["identityAssertions"]) ? payload["identityAssertions"] : Array.isArray(payload["identity_assertions"]) ? payload["identity_assertions"] : [];
661954
+ for (const item of assertions) {
661955
+ if (item && typeof item === "object") add3(item["name"]);
661956
+ }
661957
+ return names;
661958
+ }
661959
+ function isRejectedObjectLabel(label, payload) {
661960
+ const key = normalizeLabel(label).toLowerCase();
661961
+ if (!key) return true;
661962
+ if (/^(visual_identity|pending_visual_identity|person|face|speaker)(?:[_:\s-]|$)/i.test(label)) return true;
661963
+ if (identityNames(payload).has(key)) return true;
661964
+ return false;
661965
+ }
661966
+ function extractExplicitVisualObjectLabels(payload) {
661967
+ const media = payload["media"] && typeof payload["media"] === "object" ? payload["media"] : {};
661968
+ const candidates = uniqueLabels([
661969
+ ...stringList(payload["object_label"]),
661970
+ ...stringList(payload["objectLabel"]),
661971
+ ...stringList(payload["object_labels"]),
661972
+ ...stringList(payload["objectLabels"]),
661973
+ ...stringList(payload["visualObjectLabel"]),
661974
+ ...stringList(payload["visual_object_label"]),
661975
+ ...stringList(payload["visualObjectLabels"]),
661976
+ ...stringList(payload["visual_object_labels"]),
661977
+ ...stringList(payload["label"]),
661978
+ ...stringList(payload["labels"]),
661979
+ ...stringList(media["objectLabel"]),
661980
+ ...stringList(media["object_label"]),
661981
+ ...stringList(media["objectLabels"]),
661982
+ ...stringList(media["object_labels"]),
661983
+ ...stringList(media["label"]),
661984
+ ...stringList(media["labels"]),
661985
+ ...explicitCaptionLabels(payload["caption"]),
661986
+ ...explicitCaptionLabels(media["caption"])
661987
+ ]).filter((label) => !isRejectedObjectLabel(label, payload));
661988
+ const aliases = uniqueLabels([
661989
+ ...stringList(payload["aliases"]),
661990
+ ...stringList(payload["object_aliases"]),
661991
+ ...stringList(payload["objectAliases"]),
661992
+ ...stringList(payload["visualObjectAliases"]),
661993
+ ...stringList(payload["visual_object_aliases"]),
661994
+ ...stringList(media["aliases"]),
661995
+ ...stringList(media["objectAliases"]),
661996
+ ...stringList(media["object_aliases"]),
661997
+ ...candidates.slice(1)
661998
+ ]).filter((label) => !isRejectedObjectLabel(label, payload));
661999
+ return {
662000
+ label: candidates[0],
662001
+ aliases,
662002
+ source: candidates.length > 0 ? "explicit_visual_object_label" : void 0
662003
+ };
662004
+ }
662005
+ function formatVisualObjectMemoryContext(result) {
662006
+ if (!result.taught || !result.label) return "";
662007
+ const lines = [
662008
+ "Visual Object Memory",
662009
+ `- Learned object label: ${result.label}`
662010
+ ];
662011
+ if (result.aliases.length > 0) lines.push(`- Aliases: ${result.aliases.join(", ")}`);
662012
+ return lines.join("\n");
662013
+ }
662014
+ async function associateVisualObjectFromImage(options2) {
662015
+ const payload = options2.payload ?? {};
662016
+ const extraction = extractExplicitVisualObjectLabels({
662017
+ ...payload,
662018
+ media: options2.media ?? payload["media"]
662019
+ });
662020
+ if (!extraction.label) {
662021
+ return {
662022
+ attempted: false,
662023
+ taught: false,
662024
+ aliases: [],
662025
+ contextBlock: ""
662026
+ };
662027
+ }
662028
+ const visual = options2.visualMemoryTool ?? new VisualMemoryTool();
662029
+ const aliases = uniqueLabels(extraction.aliases.filter((alias) => alias.toLowerCase() !== extraction.label.toLowerCase()));
662030
+ const labels = uniqueLabels([extraction.label, ...aliases]);
662031
+ const teach = await visual.execute({
662032
+ action: "teach",
662033
+ image: options2.imagePath,
662034
+ label: extraction.label,
662035
+ aliases,
662036
+ labels,
662037
+ sourceSurface: options2.sourceSurface,
662038
+ sessionId: options2.sessionId,
662039
+ fileUniqueId: options2.media?.fileUniqueId
662040
+ });
662041
+ if (!teach.success) {
662042
+ return {
662043
+ attempted: true,
662044
+ taught: false,
662045
+ label: extraction.label,
662046
+ aliases,
662047
+ contextBlock: "",
662048
+ output: teach.output,
662049
+ degradedReason: teach.error || teach.output || `visual_memory teach failed for ${basename35(options2.imagePath)}`
662050
+ };
662051
+ }
662052
+ const result = {
662053
+ attempted: true,
662054
+ taught: true,
662055
+ label: extraction.label,
662056
+ aliases,
662057
+ contextBlock: "",
662058
+ output: teach.output
662059
+ };
662060
+ result.contextBlock = formatVisualObjectMemoryContext(result);
662061
+ return result;
662062
+ }
662063
+ var init_visual_object_association = __esm({
662064
+ "packages/cli/src/tui/visual-object-association.ts"() {
662065
+ "use strict";
662066
+ init_dist5();
662067
+ }
662068
+ });
662069
+
661174
662070
  // packages/cli/src/tui/telegram-channel-dmn.ts
661175
662071
  import {
661176
662072
  existsSync as existsSync144,
@@ -663671,55 +664567,16 @@ function advancedOcr(imagePath) {
663671
664567
  async function queryVisionModel(modelName, imagePath, prompt = "Describe what you see in this image in detail. Include any text, UI elements, code, diagrams, or visual content.") {
663672
664568
  if (!isVisionModel(modelName)) return "";
663673
664569
  if (!existsSync145(imagePath)) return "";
663674
- const imageBuffer = readFileSync118(imagePath);
663675
- const base64Image = imageBuffer.toString("base64");
663676
- const broker = getModelBroker();
663677
- const decision2 = await broker.ensureModelLoadable({
663678
- name: modelName,
663679
- domain: "vision",
663680
- host: "ollama",
663681
- owner: "vision-ingress",
663682
- requestedNumCtx: 2048
663683
- });
663684
- let effectiveModel = modelName;
663685
- let numCtx;
663686
- if (decision2.kind === "reject") {
663687
- return "";
663688
- } else if (decision2.kind === "degrade") {
663689
- effectiveModel = decision2.fallback.name;
663690
- } else if (decision2.kind === "evict") {
663691
- for (const target of decision2.evictTargets) {
663692
- await broker.evict(target.host, target.name, "make-room-for-vision");
663693
- }
663694
- numCtx = decision2.effectiveNumCtx;
663695
- } else if (decision2.kind === "ok") {
663696
- numCtx = decision2.effectiveNumCtx;
663697
- } else if (decision2.kind === "wait-for-inflight") {
663698
- const inner = await decision2.promise;
663699
- if (inner.kind === "ok") numCtx = inner.effectiveNumCtx;
663700
- else if (inner.kind === "degrade") effectiveModel = inner.fallback.name;
663701
- else if (inner.kind === "reject") return "";
663702
- }
663703
- if (numCtx === void 0) {
663704
- const trainCtx = await broker.getNctxTrain(effectiveModel);
663705
- numCtx = trainCtx && trainCtx > 0 ? Math.min(trainCtx, 4096) : 2048;
663706
- }
663707
664570
  try {
663708
- const response = await fetch("http://localhost:11434/api/generate", {
663709
- method: "POST",
663710
- headers: { "Content-Type": "application/json" },
663711
- body: JSON.stringify({
663712
- model: effectiveModel,
663713
- prompt,
663714
- images: [base64Image],
663715
- stream: false,
663716
- options: { temperature: 0.3, num_predict: 1024, num_ctx: numCtx }
663717
- })
664571
+ const tool = new VisionTool(process.cwd());
664572
+ const result = await tool.execute({
664573
+ image: imagePath,
664574
+ action: "query",
664575
+ prompt,
664576
+ model: modelName
663718
664577
  });
663719
- if (!response.ok) return "";
663720
- broker.touch("ollama", effectiveModel);
663721
- const data = await response.json();
663722
- return (data.response || "").trim();
664578
+ if (!result.success) return "";
664579
+ return String(result.llmContent || result.output || "").trim();
663723
664580
  } catch {
663724
664581
  return "";
663725
664582
  }
@@ -663790,13 +664647,13 @@ import {
663790
664647
  statSync as statSync50,
663791
664648
  statfsSync as statfsSync7,
663792
664649
  readFileSync as readFileSync119,
663793
- writeFileSync as writeFileSync78,
664650
+ writeFileSync as writeFileSync77,
663794
664651
  appendFileSync as appendFileSync15
663795
664652
  } from "node:fs";
663796
664653
  import {
663797
664654
  join as join158,
663798
664655
  resolve as resolve62,
663799
- basename as basename35,
664656
+ basename as basename36,
663800
664657
  relative as relative16,
663801
664658
  isAbsolute as isAbsolute11,
663802
664659
  extname as extname21
@@ -666265,6 +667122,7 @@ var init_telegram_bridge = __esm({
666265
667122
  init_soul_observations();
666266
667123
  init_identity_memory_tool();
666267
667124
  init_visual_identity_association();
667125
+ init_visual_object_association();
666268
667126
  init_telegram_channel_dmn();
666269
667127
  init_telegram_reflection_corpus();
666270
667128
  init_memory_paths();
@@ -669265,6 +670123,14 @@ ${mediaContext}` : ""
669265
670123
  const messageText = isReplyMedia ? msg.replyContext?.text || msg.replyContext?.caption || msg.replyToText || media.caption : msg.text || media.caption;
669266
670124
  const sender = isReplyMedia ? this.telegramMemorySenderFromReply(msg) : this.telegramMemorySenderFromMessage(msg);
669267
670125
  const modality = media.type === "audio" || media.type === "voice" ? "audio" : telegramMediaIsImage(media) ? "visual" : "text";
670126
+ const objectLabels = extractExplicitVisualObjectLabels({
670127
+ caption: media.caption,
670128
+ media: { caption: media.caption }
670129
+ });
670130
+ const structuredObjectLabels = [
670131
+ objectLabels.label,
670132
+ ...objectLabels.aliases
670133
+ ].filter((label) => typeof label === "string" && label.trim().length > 0);
669268
670134
  const payload = {
669269
670135
  sourceSurface: "telegram",
669270
670136
  sessionId: this.sessionKeyForMessage(msg),
@@ -669300,8 +670166,12 @@ ${mediaContext}` : ""
669300
670166
  sender_id: sender?.id,
669301
670167
  username: sender?.username,
669302
670168
  display_name: sender?.displayName,
669303
- labels: [media.caption].filter(Boolean)
670169
+ labels: structuredObjectLabels
669304
670170
  };
670171
+ if (objectLabels.label) {
670172
+ payload.object_label = objectLabels.label;
670173
+ payload.aliases = objectLabels.aliases;
670174
+ }
669305
670175
  if (extractedContent) {
669306
670176
  if (modality === "audio") payload.transcript = extractedContent;
669307
670177
  else payload.extractedContent = extractedContent;
@@ -670202,7 +671072,7 @@ ${mediaContext}` : ""
670202
671072
  autoFollowup: false
670203
671073
  }
670204
671074
  };
670205
- writeFileSync78(
671075
+ writeFileSync77(
670206
671076
  this.telegramConversationPath(sessionKey),
670207
671077
  JSON.stringify(payload, null, 2) + "\n",
670208
671078
  "utf8"
@@ -671031,7 +671901,7 @@ ${mediaContext}` : ""
671031
671901
  }
671032
671902
  const matchingEntry = mediaEntries.find((entry) => {
671033
671903
  if (resolve62(entry.localPath) === resolve62(raw)) return true;
671034
- if (basename35(entry.localPath) === raw) return true;
671904
+ if (basename36(entry.localPath) === raw) return true;
671035
671905
  if (entry.fileUniqueId === raw || entry.fileId === raw) return true;
671036
671906
  if (entry.messageId && String(entry.messageId) === raw) return true;
671037
671907
  if (entry.messageId && `message_id:${entry.messageId}` === raw.toLowerCase())
@@ -671072,7 +671942,7 @@ ${mediaContext}` : ""
671072
671942
  sourceMessageId,
671073
671943
  chatKey,
671074
671944
  mediaKind,
671075
- safeAlias: basename35(result.path)
671945
+ safeAlias: basename36(result.path)
671076
671946
  }
671077
671947
  };
671078
671948
  }
@@ -671113,7 +671983,7 @@ ${mediaContext}` : ""
671113
671983
  }
671114
671984
  return entries.find((entry2) => {
671115
671985
  if (resolve62(entry2.localPath) === resolve62(ref)) return true;
671116
- if (basename35(entry2.localPath) === ref) return true;
671986
+ if (basename36(entry2.localPath) === ref) return true;
671117
671987
  if (entry2.fileUniqueId === ref || entry2.fileId === ref) return true;
671118
671988
  if (entry2.messageId && String(entry2.messageId) === ref) return true;
671119
671989
  return false;
@@ -671141,7 +672011,7 @@ ${mediaContext}` : ""
671141
672011
  caption: entry.caption
671142
672012
  },
671143
672013
  modality,
671144
- label: `Telegram message_id ${entry.messageId || "unknown"} ${basename35(entry.localPath)}`,
672014
+ label: `Telegram message_id ${entry.messageId || "unknown"} ${basename36(entry.localPath)}`,
671145
672015
  extractedContent: entry.extractedContent
671146
672016
  };
671147
672017
  }
@@ -672307,8 +673177,8 @@ ${cardLines.join("\n")}`
672307
673177
  const caption = entry.caption ? ` caption=${telegramContextJsonString(entry.caption, 120)}` : "";
672308
673178
  const extracted = entry.extractedContent ? `
672309
673179
  extracted=${telegramContextJsonString(entry.extractedContent.replace(/\s+/g, " "), 220)}` : "";
672310
- const alias = entry.messageId ? `message_id:${entry.messageId}` : basename35(entry.localPath);
672311
- return `- ${alias}${replyMark}: ${kind}; file ${basename35(entry.localPath)}${caption}${extracted}`;
673180
+ const alias = entry.messageId ? `message_id:${entry.messageId}` : basename36(entry.localPath);
673181
+ return `- ${alias}${replyMark}: ${kind}; file ${basename36(entry.localPath)}${caption}${extracted}`;
672312
673182
  });
672313
673183
  sections.push(
672314
673184
  [
@@ -674805,7 +675675,7 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`
674805
675675
  throw e2;
674806
675676
  }
674807
675677
  }
674808
- writeFileSync78(
675678
+ writeFileSync77(
674809
675679
  lockFile,
674810
675680
  JSON.stringify(
674811
675681
  {
@@ -679758,7 +680628,7 @@ Scoped workspace: ${scopedRoot}`,
679758
680628
  }
679759
680629
  writeTelegramToolButtonState(state) {
679760
680630
  mkdirSync89(this.telegramToolButtonDir, { recursive: true });
679761
- writeFileSync78(
680631
+ writeFileSync77(
679762
680632
  this.telegramToolButtonPath(state.nonce),
679763
680633
  JSON.stringify(state, null, 2) + "\n",
679764
680634
  "utf-8"
@@ -680661,12 +681531,12 @@ Advanced text extraction signal: chars=${advancedSignal.chars}, lines=${advanced
680661
681531
  };
680662
681532
  }
680663
681533
  const lines = entries.map((entry, index) => {
680664
- const pathAlias = entry.messageId ? `message_id:${entry.messageId}` : basename35(entry.localPath);
681534
+ const pathAlias = entry.messageId ? `message_id:${entry.messageId}` : basename36(entry.localPath);
680665
681535
  const parts = [
680666
681536
  `${index + 1}. message_id ${entry.messageId || "unknown"}`,
680667
681537
  currentMsg?.replyToMessageId === entry.messageId ? "replied-to" : "",
680668
681538
  telegramCachedMediaIsImage(entry) ? "image" : telegramCachedMediaIsPdf(entry) ? "pdf" : telegramCachedMediaIsAudio(entry) ? "audio" : telegramCachedMediaIsVideo(entry) ? "video" : entry.mediaType,
680669
- `file=${basename35(entry.localPath)}`,
681539
+ `file=${basename36(entry.localPath)}`,
680670
681540
  `path_alias=${pathAlias}`,
680671
681541
  entry.caption ? `caption=${telegramContextJsonString(entry.caption, 140)}` : ""
680672
681542
  ].filter(Boolean);
@@ -680849,8 +681719,8 @@ Advanced text extraction signal: chars=${advancedSignal.chars}, lines=${advanced
680849
681719
  )) {
680850
681720
  return {
680851
681721
  success: true,
680852
- output: `Telegram file already sent in this turn: ${basename35(file.path)} as ${kind} to ${String(target.chatId)}`,
680853
- llmContent: `Already sent ${basename35(file.path)} to Telegram as ${kind}; do not send it again.`,
681722
+ output: `Telegram file already sent in this turn: ${basename36(file.path)} as ${kind} to ${String(target.chatId)}`,
681723
+ llmContent: `Already sent ${basename36(file.path)} to Telegram as ${kind}; do not send it again.`,
680854
681724
  durationMs: performance.now() - start2,
680855
681725
  mutated: false,
680856
681726
  mutatedFiles: []
@@ -680877,8 +681747,8 @@ Advanced text extraction signal: chars=${advancedSignal.chars}, lines=${advanced
680877
681747
  );
680878
681748
  return {
680879
681749
  success: true,
680880
- output: `Sent Telegram file: ${basename35(file.path)} as ${kind} to ${String(target.chatId)}${messageId ? ` (message_id ${messageId})` : ""}`,
680881
- llmContent: `Sent ${basename35(file.path)} to Telegram as ${kind}.`,
681750
+ output: `Sent Telegram file: ${basename36(file.path)} as ${kind} to ${String(target.chatId)}${messageId ? ` (message_id ${messageId})` : ""}`,
681751
+ llmContent: `Sent ${basename36(file.path)} to Telegram as ${kind}.`,
680882
681752
  durationMs: performance.now() - start2,
680883
681753
  mutated: false,
680884
681754
  mutatedFiles: []
@@ -681164,6 +682034,7 @@ ${visionContext}]`;
681164
682034
  cacheEntry.extractedContent
681165
682035
  );
681166
682036
  let visualIdentityContext = "";
682037
+ let visualObjectContext = "";
681167
682038
  let ingestReachedDaemon = false;
681168
682039
  try {
681169
682040
  const ingestResponse = await fetch(
@@ -681181,6 +682052,9 @@ ${visionContext}]`;
681181
682052
  const block = ingested?.visualIdentity?.contextBlock;
681182
682053
  if (typeof block === "string" && block.trim())
681183
682054
  visualIdentityContext = block.trim();
682055
+ const objectBlock = ingested?.visualObjectMemory?.contextBlock;
682056
+ if (typeof objectBlock === "string" && objectBlock.trim())
682057
+ visualObjectContext = objectBlock.trim();
681184
682058
  }
681185
682059
  } catch {
681186
682060
  }
@@ -681202,8 +682076,29 @@ ${visionContext}]`;
681202
682076
  visualIdentityContext = association.contextBlock;
681203
682077
  } catch {
681204
682078
  }
682079
+ try {
682080
+ const objectMemory = await associateVisualObjectFromImage({
682081
+ repoRoot: this.repoRoot,
682082
+ imagePath: localPath,
682083
+ sourceSurface: "telegram",
682084
+ scope: ingestPayload["scope"],
682085
+ sender: ingestPayload["sender"],
682086
+ message: ingestPayload["message"],
682087
+ replyTo: ingestPayload["replyTo"],
682088
+ sessionId: typeof ingestPayload["sessionId"] === "string" ? ingestPayload["sessionId"] : this.sessionKeyForMessage(msg),
682089
+ media: ingestPayload["media"],
682090
+ extractedContent: cacheEntry.extractedContent,
682091
+ payload: ingestPayload
682092
+ });
682093
+ if (objectMemory.contextBlock)
682094
+ visualObjectContext = objectMemory.contextBlock;
682095
+ } catch {
682096
+ }
681205
682097
  }
681206
- description = appendMediaContextBlock(description, visualIdentityContext);
682098
+ description = appendMediaContextBlock(
682099
+ description,
682100
+ [visualIdentityContext, visualObjectContext].filter(Boolean).join("\n\n")
682101
+ );
681207
682102
  } else if (isImageMedia) {
681208
682103
  description = `[${sourceLabel}image received: path_alias=${mediaAlias}${safeCaption}. Full visual comprehension pending; use image='${source === "reply" ? "reply" : "latest"}' or image='${mediaAlias}' with telegram_image_analyze detail='full'.]`;
681209
682104
  cacheEntry.analysisComplete = false;
@@ -681512,7 +682407,7 @@ ${text2}`.trim()
681512
682407
  throw new Error(`File does not exist: ${media.value}`);
681513
682408
  const buffer2 = readFileSync119(media.value);
681514
682409
  const boundary = `----omnius-media-${Date.now()}-${Math.random().toString(36).slice(2)}`;
681515
- const filename = basename35(media.value);
682410
+ const filename = basename36(media.value);
681516
682411
  const contentType = mimeForPath(media.value, media.kind);
681517
682412
  const parts = [];
681518
682413
  const addField = (name10, value2) => {
@@ -681750,7 +682645,7 @@ Content-Type: ${contentType}\r
681750
682645
  audioAsVoice: false
681751
682646
  },
681752
682647
  {
681753
- caption: `Vision action loop screenshot: ${basename35(abs)}`
682648
+ caption: `Vision action loop screenshot: ${basename36(abs)}`
681754
682649
  }
681755
682650
  ).catch(() => null);
681756
682651
  }
@@ -681830,7 +682725,7 @@ Content-Type: ${contentType}\r
681830
682725
  continue;
681831
682726
  }
681832
682727
  const buffer2 = readFileSync119(pathOrFileId);
681833
- const filename = basename35(pathOrFileId);
682728
+ const filename = basename36(pathOrFileId);
681834
682729
  parts.push(Buffer.from(`--${boundary}\r
681835
682730
  `));
681836
682731
  parts.push(
@@ -683607,9 +684502,9 @@ __export(projects_exports, {
683607
684502
  setCurrentProject: () => setCurrentProject,
683608
684503
  unregisterProject: () => unregisterProject
683609
684504
  });
683610
- import { readFileSync as readFileSync120, writeFileSync as writeFileSync79, mkdirSync as mkdirSync90, existsSync as existsSync147, statSync as statSync51, renameSync as renameSync12 } from "node:fs";
684505
+ import { readFileSync as readFileSync120, writeFileSync as writeFileSync78, mkdirSync as mkdirSync90, existsSync as existsSync147, statSync as statSync51, renameSync as renameSync12 } from "node:fs";
683611
684506
  import { homedir as homedir52 } from "node:os";
683612
- import { basename as basename36, join as join159, resolve as resolve63 } from "node:path";
684507
+ import { basename as basename37, join as join159, resolve as resolve63 } from "node:path";
683613
684508
  import { randomUUID as randomUUID19 } from "node:crypto";
683614
684509
  function readAll2() {
683615
684510
  try {
@@ -683625,7 +684520,7 @@ function readAll2() {
683625
684520
  function writeAll(file) {
683626
684521
  mkdirSync90(OMNIUS_DIR3, { recursive: true });
683627
684522
  const tmp = `${PROJECTS_FILE}.${randomUUID19().slice(0, 8)}.tmp`;
683628
- writeFileSync79(tmp, JSON.stringify(file, null, 2), "utf8");
684523
+ writeFileSync78(tmp, JSON.stringify(file, null, 2), "utf8");
683629
684524
  renameSync12(tmp, PROJECTS_FILE);
683630
684525
  }
683631
684526
  function listProjects() {
@@ -683657,7 +684552,7 @@ function registerProject(root, pid) {
683657
684552
  } else {
683658
684553
  entry = {
683659
684554
  root: canonical,
683660
- name: basename36(canonical) || canonical,
684555
+ name: basename37(canonical) || canonical,
683661
684556
  firstSeen: now2,
683662
684557
  lastSeen: now2,
683663
684558
  pid: pid ?? null,
@@ -683708,7 +684603,7 @@ function setCurrentProject(root) {
683708
684603
  currentRoot = canonical;
683709
684604
  try {
683710
684605
  mkdirSync90(OMNIUS_DIR3, { recursive: true });
683711
- writeFileSync79(CURRENT_FILE, `${canonical}
684606
+ writeFileSync78(CURRENT_FILE, `${canonical}
683712
684607
  `, "utf8");
683713
684608
  } catch {
683714
684609
  }
@@ -684605,7 +685500,7 @@ var init_access_policy = __esm({
684605
685500
 
684606
685501
  // packages/cli/src/api/project-preferences.ts
684607
685502
  import { createHash as createHash42 } from "node:crypto";
684608
- import { existsSync as existsSync148, mkdirSync as mkdirSync91, readFileSync as readFileSync121, renameSync as renameSync13, writeFileSync as writeFileSync80, unlinkSync as unlinkSync34 } from "node:fs";
685503
+ import { existsSync as existsSync148, mkdirSync as mkdirSync91, readFileSync as readFileSync121, renameSync as renameSync13, writeFileSync as writeFileSync79, unlinkSync as unlinkSync34 } from "node:fs";
684609
685504
  import { homedir as homedir53 } from "node:os";
684610
685505
  import { join as join160, resolve as resolve64 } from "node:path";
684611
685506
  import { randomUUID as randomUUID20 } from "node:crypto";
@@ -684628,7 +685523,7 @@ function ensureDir(root) {
684628
685523
  const sentinel = rootSentinelPath(root);
684629
685524
  try {
684630
685525
  if (!existsSync148(sentinel)) {
684631
- writeFileSync80(sentinel, `${resolve64(root)}
685526
+ writeFileSync79(sentinel, `${resolve64(root)}
684632
685527
  `, "utf8");
684633
685528
  }
684634
685529
  } catch {
@@ -684657,12 +685552,12 @@ function writeProjectPreferences(root, partial) {
684657
685552
  };
684658
685553
  const file = prefsPath(root);
684659
685554
  const tmp = `${file}.${randomUUID20().slice(0, 8)}.tmp`;
684660
- writeFileSync80(tmp, JSON.stringify(merged, null, 2), "utf8");
685555
+ writeFileSync79(tmp, JSON.stringify(merged, null, 2), "utf8");
684661
685556
  try {
684662
685557
  renameSync13(tmp, file);
684663
685558
  } catch (err) {
684664
685559
  try {
684665
- writeFileSync80(file, JSON.stringify(merged, null, 2), "utf8");
685560
+ writeFileSync79(file, JSON.stringify(merged, null, 2), "utf8");
684666
685561
  } catch {
684667
685562
  }
684668
685563
  try {
@@ -685067,7 +685962,7 @@ data: ${JSON.stringify(ev)}
685067
685962
 
685068
685963
  // packages/cli/src/api/routes-media.ts
685069
685964
  import { existsSync as existsSync151, mkdirSync as mkdirSync94, statSync as statSync53, copyFileSync as copyFileSync7, createReadStream } from "node:fs";
685070
- import { basename as basename37, join as join162, resolve as pathResolve2 } from "node:path";
685965
+ import { basename as basename38, join as join162, resolve as pathResolve2 } from "node:path";
685071
685966
  function mediaWorkDir() {
685072
685967
  return join162(omniusHomeDir(), "media", "_work");
685073
685968
  }
@@ -685089,7 +685984,7 @@ function extractGeneratedPath(output) {
685089
685984
  function publishToGallery(srcPath, kind) {
685090
685985
  ensureGlobalMediaDirs();
685091
685986
  const dir = globalMediaDir(kind);
685092
- const name10 = basename37(srcPath);
685987
+ const name10 = basename38(srcPath);
685093
685988
  const dest = join162(dir, name10);
685094
685989
  try {
685095
685990
  if (pathResolve2(srcPath) !== pathResolve2(dest)) {
@@ -685290,7 +686185,7 @@ async function handleAvAnalyze(ctx3) {
685290
686185
  adapterStatus[role] = a2 && !a2.meta.isMock ? "live" : "mock";
685291
686186
  if (adapterStatus[role] === "live") anyLive = true;
685292
686187
  }
685293
- const episodeId = `rest-${basename37(filePath)}-${Date.now().toString(36)}`;
686188
+ const episodeId = `rest-${basename38(filePath)}-${Date.now().toString(36)}`;
685294
686189
  const ingest = await ingestMedia(episodeId, `file://${filePath}`);
685295
686190
  const store2 = await analyzeEpisode({ episodeId, mediaUri: `file://${filePath}`, durationSec: ingest.source.durationSec, registry: registry4, windows: ingest.shots, roles: AV_ROLES });
685296
686191
  const world = store2.snapshot();
@@ -685460,7 +686355,7 @@ function handleFile(ctx3) {
685460
686355
  return true;
685461
686356
  }
685462
686357
  const dir = globalMediaDir(kind);
685463
- const full = pathResolve2(dir, basename37(name10));
686358
+ const full = pathResolve2(dir, basename38(name10));
685464
686359
  if (!full.startsWith(pathResolve2(dir)) || !existsSync151(full)) {
685465
686360
  sendProblem(ctx3.res, problemDetails({
685466
686361
  type: P.notFound,
@@ -685705,7 +686600,7 @@ var init_direct_tool_registry = __esm({
685705
686600
  });
685706
686601
 
685707
686602
  // packages/cli/src/api/external-tool-registry.ts
685708
- import { existsSync as existsSync152, mkdirSync as mkdirSync95, readFileSync as readFileSync123, writeFileSync as writeFileSync81, renameSync as renameSync14 } from "node:fs";
686603
+ import { existsSync as existsSync152, mkdirSync as mkdirSync95, readFileSync as readFileSync123, writeFileSync as writeFileSync80, renameSync as renameSync14 } from "node:fs";
685709
686604
  import { join as join163 } from "node:path";
685710
686605
  function externalToolStorePath(workingDir) {
685711
686606
  return join163(workingDir, ".omnius", "external-tools.json");
@@ -685728,7 +686623,7 @@ function persist3(workingDir, tools) {
685728
686623
  mkdirSync95(join163(workingDir, ".omnius"), { recursive: true });
685729
686624
  const file = { version: STORE_VERSION, tools };
685730
686625
  const tmp = `${path12}.tmp`;
685731
- writeFileSync81(tmp, JSON.stringify(file, null, 2), { mode: 384 });
686626
+ writeFileSync80(tmp, JSON.stringify(file, null, 2), { mode: 384 });
685732
686627
  renameSync14(tmp, path12);
685733
686628
  }
685734
686629
  function validateManifest2(input, existing) {
@@ -686840,7 +687735,7 @@ __export(runtime_keys_exports, {
686840
687735
  mintKey: () => mintKey,
686841
687736
  revokeByPrefix: () => revokeByPrefix
686842
687737
  });
686843
- import { existsSync as existsSync154, readFileSync as readFileSync125, writeFileSync as writeFileSync82, mkdirSync as mkdirSync96, chmodSync as chmodSync4 } from "node:fs";
687738
+ import { existsSync as existsSync154, readFileSync as readFileSync125, writeFileSync as writeFileSync81, mkdirSync as mkdirSync96, chmodSync as chmodSync4 } from "node:fs";
686844
687739
  import { join as join165 } from "node:path";
686845
687740
  import { homedir as homedir55 } from "node:os";
686846
687741
  import { randomBytes as randomBytes27 } from "node:crypto";
@@ -686861,7 +687756,7 @@ function loadAll() {
686861
687756
  }
686862
687757
  function persistAll(records) {
686863
687758
  ensureDir2();
686864
- writeFileSync82(KEYS_FILE, JSON.stringify(records, null, 2), "utf-8");
687759
+ writeFileSync81(KEYS_FILE, JSON.stringify(records, null, 2), "utf-8");
686865
687760
  try {
686866
687761
  chmodSync4(KEYS_FILE, 384);
686867
687762
  } catch {
@@ -687095,7 +687990,7 @@ __export(graphical_sudo_exports, {
687095
687990
  runGraphicalSudo: () => runGraphicalSudo
687096
687991
  });
687097
687992
  import { spawn as spawn31 } from "node:child_process";
687098
- import { existsSync as existsSync156, mkdirSync as mkdirSync97, writeFileSync as writeFileSync83, chmodSync as chmodSync5 } from "node:fs";
687993
+ import { existsSync as existsSync156, mkdirSync as mkdirSync97, writeFileSync as writeFileSync82, chmodSync as chmodSync5 } from "node:fs";
687099
687994
  import { join as join167 } from "node:path";
687100
687995
  import { tmpdir as tmpdir22 } from "node:os";
687101
687996
  function detectSudoHelper() {
@@ -687131,7 +688026,7 @@ exec zenity --password --title="Omnius needs sudo" --text="${description.replace
687131
688026
  exec kdialog --password "${description.replace(/"/g, '\\"')}" 2>/dev/null
687132
688027
  `;
687133
688028
  }
687134
- writeFileSync83(shim, body, "utf-8");
688029
+ writeFileSync82(shim, body, "utf-8");
687135
688030
  chmodSync5(shim, 493);
687136
688031
  return shim;
687137
688032
  }
@@ -702167,7 +703062,7 @@ var init_auth_oidc = __esm({
702167
703062
  });
702168
703063
 
702169
703064
  // packages/cli/src/api/usage-tracker.ts
702170
- import { mkdirSync as mkdirSync99, readFileSync as readFileSync128, writeFileSync as writeFileSync84, existsSync as existsSync158 } from "node:fs";
703065
+ import { mkdirSync as mkdirSync99, readFileSync as readFileSync128, writeFileSync as writeFileSync83, existsSync as existsSync158 } from "node:fs";
702171
703066
  import { join as join169 } from "node:path";
702172
703067
  function initUsageTracker(omniusDir) {
702173
703068
  const dir = join169(omniusDir, "usage");
@@ -702211,7 +703106,7 @@ function flush2() {
702211
703106
  if (!initialized2 || !dirty) return;
702212
703107
  try {
702213
703108
  store.lastSaved = (/* @__PURE__ */ new Date()).toISOString();
702214
- writeFileSync84(usageFile, JSON.stringify(store, null, 2), "utf-8");
703109
+ writeFileSync83(usageFile, JSON.stringify(store, null, 2), "utf-8");
702215
703110
  dirty = false;
702216
703111
  } catch {
702217
703112
  }
@@ -702386,7 +703281,7 @@ var init_chat_followup = __esm({
702386
703281
 
702387
703282
  // packages/cli/src/docker.ts
702388
703283
  import { execSync as execSync59, spawn as spawn32 } from "node:child_process";
702389
- import { existsSync as existsSync159, mkdirSync as mkdirSync100, writeFileSync as writeFileSync85 } from "node:fs";
703284
+ import { existsSync as existsSync159, mkdirSync as mkdirSync100, writeFileSync as writeFileSync84 } from "node:fs";
702390
703285
  import { join as join170, resolve as resolve65, dirname as dirname50 } from "node:path";
702391
703286
  import { homedir as homedir58 } from "node:os";
702392
703287
  import { fileURLToPath as fileURLToPath19 } from "node:url";
@@ -702611,8 +703506,8 @@ chown -R node:node /workspace /home/node/.omnius 2>/dev/null || true
702611
703506
  if [ "$1" = "omnius" ]; then shift; exec su - node -c "cd /workspace && omnius $*"; fi
702612
703507
  exec "$@"
702613
703508
  `;
702614
- writeFileSync85(join170(dir, "Dockerfile"), dockerfile);
702615
- writeFileSync85(join170(dir, "docker-entrypoint.sh"), entrypoint, { mode: 493 });
703509
+ writeFileSync84(join170(dir, "Dockerfile"), dockerfile);
703510
+ writeFileSync84(join170(dir, "docker-entrypoint.sh"), entrypoint, { mode: 493 });
702616
703511
  }
702617
703512
  function hasNvidiaGpu() {
702618
703513
  try {
@@ -702685,7 +703580,7 @@ __export(embedding_workers_exports, {
702685
703580
  startEmbeddingWorkers: () => startEmbeddingWorkers,
702686
703581
  stopEmbeddingWorkers: () => stopEmbeddingWorkers
702687
703582
  });
702688
- import { basename as basename38, join as join171 } from "node:path";
703583
+ import { basename as basename39, join as join171 } from "node:path";
702689
703584
  function startEmbeddingWorkers(opts) {
702690
703585
  if (_running) return;
702691
703586
  _running = true;
@@ -702751,7 +703646,7 @@ async function runEmbeddingTask(modality, episodeId, taskId, opts) {
702751
703646
  try {
702752
703647
  if (!_aligner) {
702753
703648
  const stateRoot = process.env.OMNIUS_DIR || process.cwd();
702754
- const omniusDir = basename38(stateRoot) === ".omnius" ? stateRoot : join171(stateRoot, ".omnius");
703649
+ const omniusDir = basename39(stateRoot) === ".omnius" ? stateRoot : join171(stateRoot, ".omnius");
702755
703650
  const memDir = join171(omniusDir, "memory");
702756
703651
  _aligner = new EmbeddingAligner(
702757
703652
  `${modality}-${emb.length}`,
@@ -702873,7 +703768,7 @@ import { spawn as spawn33, execSync as execSync60 } from "node:child_process";
702873
703768
  import {
702874
703769
  createReadStream as createReadStream2,
702875
703770
  mkdirSync as mkdirSync101,
702876
- writeFileSync as writeFileSync86,
703771
+ writeFileSync as writeFileSync85,
702877
703772
  readFileSync as readFileSync129,
702878
703773
  readdirSync as readdirSync56,
702879
703774
  existsSync as existsSync160,
@@ -704534,11 +705429,11 @@ function atomicJobWrite(dir, id, job) {
704534
705429
  const finalPath = join172(dir, `${id}.json`);
704535
705430
  const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
704536
705431
  try {
704537
- writeFileSync86(tmpPath, JSON.stringify(job, null, 2), "utf-8");
705432
+ writeFileSync85(tmpPath, JSON.stringify(job, null, 2), "utf-8");
704538
705433
  renameSync15(tmpPath, finalPath);
704539
705434
  } catch {
704540
705435
  try {
704541
- writeFileSync86(finalPath, JSON.stringify(job, null, 2), "utf-8");
705436
+ writeFileSync85(finalPath, JSON.stringify(job, null, 2), "utf-8");
704542
705437
  } catch {
704543
705438
  }
704544
705439
  try {
@@ -706370,7 +707265,7 @@ function writeUpdateState(state) {
706370
707265
  mkdirSync101(dir, { recursive: true });
706371
707266
  const finalPath = updateStateFile();
706372
707267
  const tmpPath = `${finalPath}.tmp.${process.pid}`;
706373
- writeFileSync86(tmpPath, JSON.stringify(state, null, 2), "utf-8");
707268
+ writeFileSync85(tmpPath, JSON.stringify(state, null, 2), "utf-8");
706374
707269
  renameSync15(tmpPath, finalPath);
706375
707270
  } catch {
706376
707271
  }
@@ -708720,13 +709615,13 @@ async function handleRequest(req3, res, ollamaUrl, verbose, runtimeDefaults = {}
708720
709615
  return;
708721
709616
  }
708722
709617
  const { tmpdir: tmpdir24 } = await import("node:os");
708723
- const { writeFileSync: writeFileSync91, unlinkSync: unlinkSync36 } = await import("node:fs");
709618
+ const { writeFileSync: writeFileSync90, unlinkSync: unlinkSync36 } = await import("node:fs");
708724
709619
  const { join: pjoin } = await import("node:path");
708725
709620
  const tmpPath = pjoin(
708726
709621
  tmpdir24(),
708727
709622
  `omnius-clone-upload-${Date.now()}-${safeName3}`
708728
709623
  );
708729
- writeFileSync91(tmpPath, buf);
709624
+ writeFileSync90(tmpPath, buf);
708730
709625
  try {
708731
709626
  const ve = getVoiceEngine();
708732
709627
  const msg = await ve.setCloneVoice(tmpPath);
@@ -709450,7 +710345,7 @@ data: ${JSON.stringify(data)}
709450
710345
  }
709451
710346
  for (const f2 of seenFiles) {
709452
710347
  try {
709453
- writeFileSync86(f2, JSON.stringify({ tasks: [] }, null, 2));
710348
+ writeFileSync85(f2, JSON.stringify({ tasks: [] }, null, 2));
709454
710349
  deleted++;
709455
710350
  } catch {
709456
710351
  }
@@ -711249,11 +712144,11 @@ function setScheduledEnabled(id, enabled2) {
711249
712144
  arr[target.index].enabled = enabled2;
711250
712145
  if (Array.isArray(json?.tasks)) {
711251
712146
  json.tasks = arr;
711252
- writeFileSync86(target.file, JSON.stringify(json, null, 2));
712147
+ writeFileSync85(target.file, JSON.stringify(json, null, 2));
711253
712148
  } else if (Array.isArray(json)) {
711254
- writeFileSync86(target.file, JSON.stringify(arr, null, 2));
712149
+ writeFileSync85(target.file, JSON.stringify(arr, null, 2));
711255
712150
  } else {
711256
- writeFileSync86(target.file, JSON.stringify({ tasks: arr }, null, 2));
712151
+ writeFileSync85(target.file, JSON.stringify({ tasks: arr }, null, 2));
711257
712152
  }
711258
712153
  if (!enabled2) {
711259
712154
  try {
@@ -711283,11 +712178,11 @@ function deleteScheduledById(id) {
711283
712178
  arr.splice(target.index, 1);
711284
712179
  if (Array.isArray(json?.tasks)) {
711285
712180
  json.tasks = arr;
711286
- writeFileSync86(target.file, JSON.stringify(json, null, 2));
712181
+ writeFileSync85(target.file, JSON.stringify(json, null, 2));
711287
712182
  } else if (Array.isArray(json)) {
711288
- writeFileSync86(target.file, JSON.stringify(arr, null, 2));
712183
+ writeFileSync85(target.file, JSON.stringify(arr, null, 2));
711289
712184
  } else {
711290
- writeFileSync86(target.file, JSON.stringify({ tasks: arr }, null, 2));
712185
+ writeFileSync85(target.file, JSON.stringify({ tasks: arr }, null, 2));
711291
712186
  }
711292
712187
  const candidates = [];
711293
712188
  if (id) candidates.push(id);
@@ -711521,7 +712416,7 @@ function reconcileScheduledTasks(apply) {
711521
712416
  mkdirSync101(join172(wdir, ".omnius", "scheduled", "logs"), {
711522
712417
  recursive: true
711523
712418
  });
711524
- writeFileSync86(file, JSON.stringify(toWrite, null, 2));
712419
+ writeFileSync85(file, JSON.stringify(toWrite, null, 2));
711525
712420
  adopted.push({ file, index: arr.length - 1 });
711526
712421
  }
711527
712422
  } else {
@@ -711699,8 +712594,8 @@ WantedBy=timers.target
711699
712594
  `;
711700
712595
  if (!dryRun) {
711701
712596
  mkdirSync101(unitDir, { recursive: true });
711702
- writeFileSync86(svc, svcText);
711703
- writeFileSync86(tim, timText);
712597
+ writeFileSync85(svc, svcText);
712598
+ writeFileSync85(tim, timText);
711704
712599
  try {
711705
712600
  const { execSync: es } = require4("node:child_process");
711706
712601
  es("systemctl --user daemon-reload", { stdio: "pipe" });
@@ -712084,7 +712979,7 @@ function startApiServer(options2 = {}) {
712084
712979
  try {
712085
712980
  const dir = join172(homedir59(), ".omnius");
712086
712981
  mkdirSync101(dir, { recursive: true });
712087
- writeFileSync86(
712982
+ writeFileSync85(
712088
712983
  join172(dir, "access"),
712089
712984
  `${runtimeAccessMode}
712090
712985
  `,
@@ -712497,7 +713392,7 @@ function startApiServer(options2 = {}) {
712497
713392
  }
712498
713393
  try {
712499
713394
  const {
712500
- writeFileSync: writeFileSync91,
713395
+ writeFileSync: writeFileSync90,
712501
713396
  mkdirSync: mkdirSync106,
712502
713397
  existsSync: _exists,
712503
713398
  readFileSync: _rfs
@@ -712536,7 +713431,7 @@ function startApiServer(options2 = {}) {
712536
713431
  for (const dir of dirSet) {
712537
713432
  try {
712538
713433
  if (!_exists(dir)) mkdirSync106(dir, { recursive: true });
712539
- writeFileSync91(_join(dir, "api-port.json"), apiHint);
713434
+ writeFileSync90(_join(dir, "api-port.json"), apiHint);
712540
713435
  written++;
712541
713436
  } catch {
712542
713437
  }
@@ -712905,7 +713800,7 @@ async function handleChatAttachmentUpload(req3, res) {
712905
713800
  dir,
712906
713801
  `${Date.now()}-${randomUUID21().slice(0, 8)}-${safeName3}`
712907
713802
  );
712908
- writeFileSync86(localPath, Buffer.from(base642, "base64"));
713803
+ writeFileSync85(localPath, Buffer.from(base642, "base64"));
712909
713804
  const mimeType = typeof b.mimeType === "string" ? b.mimeType : typeof b.mime_type === "string" ? b.mime_type : "";
712910
713805
  const isImage = mimeType.toLowerCase().startsWith("image/") || /\.(png|jpe?g|gif|webp|bmp|tiff?)$/i.test(safeName3);
712911
713806
  const sessionId = typeof b.sessionId === "string" ? b.sessionId : typeof b.session_id === "string" ? b.session_id : void 0;
@@ -712962,6 +713857,7 @@ async function handleChatAttachmentUpload(req3, res) {
712962
713857
  graph.close();
712963
713858
  }
712964
713859
  let visualIdentity = void 0;
713860
+ let visualObjectMemory = void 0;
712965
713861
  let contextBlock = [
712966
713862
  `GUI attachment saved: ${safeName3}`,
712967
713863
  `path: ${localPath}`,
@@ -713000,6 +713896,40 @@ ${association.contextBlock}`;
713000
713896
  degradedReason: err instanceof Error ? err.message : String(err)
713001
713897
  };
713002
713898
  }
713899
+ try {
713900
+ const { associateVisualObjectFromImage: associateVisualObjectFromImage2 } = await Promise.resolve().then(() => (init_visual_object_association(), visual_object_association_exports));
713901
+ const objectMemory = await associateVisualObjectFromImage2({
713902
+ repoRoot: process.cwd(),
713903
+ imagePath: localPath,
713904
+ sourceSurface: "gui",
713905
+ scope,
713906
+ sender,
713907
+ message: message2,
713908
+ sessionId,
713909
+ media,
713910
+ payload: b
713911
+ });
713912
+ visualObjectMemory = {
713913
+ attempted: objectMemory.attempted,
713914
+ taught: objectMemory.taught,
713915
+ label: objectMemory.label,
713916
+ aliases: objectMemory.aliases,
713917
+ contextBlock: objectMemory.contextBlock,
713918
+ degradedReason: objectMemory.degradedReason
713919
+ };
713920
+ if (objectMemory.contextBlock)
713921
+ contextBlock += `
713922
+
713923
+ ${objectMemory.contextBlock}`;
713924
+ } catch (err) {
713925
+ visualObjectMemory = {
713926
+ attempted: true,
713927
+ taught: false,
713928
+ aliases: [],
713929
+ contextBlock: "",
713930
+ degradedReason: err instanceof Error ? err.message : String(err)
713931
+ };
713932
+ }
713003
713933
  }
713004
713934
  jsonResponse(res, 200, {
713005
713935
  id: ingestId,
@@ -713008,7 +713938,8 @@ ${association.contextBlock}`;
713008
713938
  mimeType,
713009
713939
  modality,
713010
713940
  contextBlock,
713011
- visualIdentity
713941
+ visualIdentity,
713942
+ visualObjectMemory
713012
713943
  });
713013
713944
  } catch (err) {
713014
713945
  jsonResponse(res, 400, {
@@ -713141,6 +714072,7 @@ async function handleMemoryIngest2(req3, res, ollamaUrl) {
713141
714072
  embeddings
713142
714073
  });
713143
714074
  let visualIdentity = void 0;
714075
+ let visualObjectMemory = void 0;
713144
714076
  if (modality === "visual" && mediaPath) {
713145
714077
  try {
713146
714078
  const { associateVisualIdentityFromImage: associateVisualIdentityFromImage2 } = await Promise.resolve().then(() => (init_visual_identity_association(), visual_identity_association_exports));
@@ -713174,8 +714106,40 @@ async function handleMemoryIngest2(req3, res, ollamaUrl) {
713174
714106
  degradedReason: err instanceof Error ? err.message : String(err)
713175
714107
  };
713176
714108
  }
714109
+ try {
714110
+ const { associateVisualObjectFromImage: associateVisualObjectFromImage2 } = await Promise.resolve().then(() => (init_visual_object_association(), visual_object_association_exports));
714111
+ const objectMemory = await associateVisualObjectFromImage2({
714112
+ repoRoot: process.cwd(),
714113
+ imagePath: mediaPath,
714114
+ sourceSurface,
714115
+ scope,
714116
+ sender,
714117
+ message: message2,
714118
+ replyTo,
714119
+ sessionId,
714120
+ media,
714121
+ extractedContent: b.extracted_content || b.extractedContent,
714122
+ payload: b
714123
+ });
714124
+ visualObjectMemory = {
714125
+ attempted: objectMemory.attempted,
714126
+ taught: objectMemory.taught,
714127
+ label: objectMemory.label,
714128
+ aliases: objectMemory.aliases,
714129
+ contextBlock: objectMemory.contextBlock,
714130
+ degradedReason: objectMemory.degradedReason
714131
+ };
714132
+ } catch (err) {
714133
+ visualObjectMemory = {
714134
+ attempted: true,
714135
+ taught: false,
714136
+ aliases: [],
714137
+ contextBlock: "",
714138
+ degradedReason: err instanceof Error ? err.message : String(err)
714139
+ };
714140
+ }
713177
714141
  }
713178
- jsonResponse(res, 200, { id: result.episodeId, ...result, visualIdentity });
714142
+ jsonResponse(res, 200, { id: result.episodeId, ...result, visualIdentity, visualObjectMemory });
713179
714143
  } catch (err) {
713180
714144
  jsonResponse(res, 400, {
713181
714145
  error: "bad_request",
@@ -713391,7 +714355,7 @@ __export(clipboard_media_exports, {
713391
714355
  pasteClipboardImageToFile: () => pasteClipboardImageToFile
713392
714356
  });
713393
714357
  import { execFileSync as execFileSync11, execSync as execSync61 } from "node:child_process";
713394
- import { mkdirSync as mkdirSync102, readFileSync as readFileSync130, rmSync as rmSync13, writeFileSync as writeFileSync87 } from "node:fs";
714358
+ import { mkdirSync as mkdirSync102, readFileSync as readFileSync130, rmSync as rmSync13, writeFileSync as writeFileSync86 } from "node:fs";
713395
714359
  import { join as join173 } from "node:path";
713396
714360
  function pasteClipboardImageToFile(repoRoot) {
713397
714361
  const image = readClipboardImage();
@@ -713399,7 +714363,7 @@ function pasteClipboardImageToFile(repoRoot) {
713399
714363
  const dir = join173(repoRoot, ".omnius", "clipboard");
713400
714364
  mkdirSync102(dir, { recursive: true });
713401
714365
  const path12 = join173(dir, `clipboard-${Date.now()}${image.ext}`);
713402
- writeFileSync87(path12, image.buffer);
714366
+ writeFileSync86(path12, image.buffer);
713403
714367
  return { path: path12, buffer: image.buffer, mime: image.mime };
713404
714368
  }
713405
714369
  function readClipboardImage() {
@@ -713468,7 +714432,7 @@ import { createRequire as createRequire9 } from "node:module";
713468
714432
  import { fileURLToPath as fileURLToPath21 } from "node:url";
713469
714433
  import {
713470
714434
  readFileSync as readFileSync131,
713471
- writeFileSync as writeFileSync88,
714435
+ writeFileSync as writeFileSync87,
713472
714436
  appendFileSync as appendFileSync17,
713473
714437
  rmSync as rmSync14,
713474
714438
  readdirSync as readdirSync57,
@@ -714595,7 +715559,7 @@ function createFanoutExploreTool(config, repoRoot, ctxWindowSize) {
714595
715559
  );
714596
715560
  if (debug) {
714597
715561
  try {
714598
- writeFileSync88(
715562
+ writeFileSync87(
714599
715563
  join174(repoRoot, ".omnius", "fanout-debug.json"),
714600
715564
  JSON.stringify({ objective, regions, rawReturns, digests }, null, 2)
714601
715565
  );
@@ -715381,7 +716345,7 @@ ${skillPack}`;
715381
716345
  You have vision capabilities. Choose the RIGHT tool for each situation:
715382
716346
 
715383
716347
  FOR IMAGE FILES (photos, screenshots, diagrams):
715384
- image_read(image="path") — Read an image file (base64 + OCR)
716348
+ image_read(path="path") — Read image metadata/base64/OCR; use vision(image="path") for semantic Moondream analysis
715385
716349
  vision(image="path", action="caption") — Describe image contents via Moondream
715386
716350
  vision(image="path", action="query", prompt="question") — Ask questions about an image
715387
716351
  vision(image="path", action="detect", prompt="object") — Find objects (bounding boxes)
@@ -716951,6 +717915,7 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
716951
717915
  },
716952
717916
  async () => {
716953
717917
  const result = await runner.run(effectiveTask, systemContext);
717918
+ _apiCallbacks?.onRunResult?.(result);
716954
717919
  const tokens = {
716955
717920
  total: result.totalTokens,
716956
717921
  estimated: result.estimatedTokens
@@ -718910,7 +719875,7 @@ This is an independent background session started from /background.`
718910
719875
  if (Math.random() < 0.02) {
718911
719876
  const all2 = readFileSync131(HISTORY_FILE, "utf8").trim().split("\n");
718912
719877
  if (all2.length > MAX_HISTORY_LINES) {
718913
- writeFileSync88(
719878
+ writeFileSync87(
718914
719879
  HISTORY_FILE,
718915
719880
  all2.slice(-MAX_HISTORY_LINES).join("\n") + "\n",
718916
719881
  "utf8"
@@ -719851,7 +720816,7 @@ Log: ${nexusLogPath}`
719851
720816
  sessionTitle = title.trim() || null;
719852
720817
  try {
719853
720818
  mkdirSync103(join174(repoRoot, ".omnius"), { recursive: true });
719854
- writeFileSync88(
720819
+ writeFileSync87(
719855
720820
  join174(repoRoot, ".omnius", "session-title"),
719856
720821
  `${sessionTitle ?? ""}
719857
720822
  `,
@@ -719945,7 +720910,7 @@ Log: ${nexusLogPath}`
719945
720910
  queuePrompt(
719946
720911
  imageContext ? `${imageContext}
719947
720912
 
719948
- The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analysis, and ASCII preview above to understand and respond to the image content.` : `The user pasted a clipboard image saved at ${relPath}. Read it with image_read or vision and respond to what is shown.`,
720913
+ The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analysis, and ASCII preview above to understand and respond to the image content.` : `The user pasted a clipboard image saved at ${relPath}. Use vision(image="${relPath}") for semantic Moondream analysis, and image_read(path="${relPath}") only if metadata/OCR/raw ingress is needed.`,
719949
720914
  "clipboard"
719950
720915
  );
719951
720916
  return {
@@ -722575,7 +723540,7 @@ ${result.text}`;
722575
723540
 
722576
723541
  ${imageContext}
722577
723542
 
722578
- Read it with image_read or vision if more detail is needed. Describe what you see and extract any text with OCR if relevant.` : `The user has provided an image file: ${cleanPath}. Read it with image_read and describe what you see. Extract any text with OCR if relevant.`;
723543
+ Use vision(image="${cleanPath}") for additional semantic detail, and image_read(path="${cleanPath}") only if metadata/OCR/raw ingress is needed.` : `The user has provided an image file: ${cleanPath}. Use vision(image="${cleanPath}") for semantic Moondream analysis. Use image_read(path="${cleanPath}") only if metadata/OCR/raw ingress is needed.`;
722579
723544
  }
722580
723545
  if (isMedia && fullInput === input && (voiceEngine.isLuxtts || voiceEngine.isMisotts)) {
722581
723546
  const cloneExts = [".wav", ".mp3", ".ogg", ".flac", ".m4a"];
@@ -723233,6 +724198,7 @@ async function runWithTUI(task, config, repoPath2, callbacks) {
723233
724198
  }
723234
724199
  }
723235
724200
  _apiCallbacks = callbacks ?? null;
724201
+ const headlessMode = Boolean(callbacks);
723236
724202
  await bootstrapMcpAndPlugins(repoRoot);
723237
724203
  renderCompactHeader(config.model);
723238
724204
  renderUserMessage(task);
@@ -723243,6 +724209,10 @@ async function runWithTUI(task, config, repoPath2, callbacks) {
723243
724209
  try {
723244
724210
  const handle2 = startTask(task, config, repoRoot);
723245
724211
  await handle2.promise;
724212
+ if (headlessMode) {
724213
+ _apiCallbacks = null;
724214
+ return;
724215
+ }
723246
724216
  try {
723247
724217
  const ikDir = join174(repoRoot, ".omnius", "identity");
723248
724218
  const ikFile = join174(ikDir, "self-state.json");
@@ -723303,7 +724273,7 @@ async function runWithTUI(task, config, repoPath2, callbacks) {
723303
724273
  );
723304
724274
  ikState.session_count = (ikState.session_count || 0) + 1;
723305
724275
  ikState.updated_at = (/* @__PURE__ */ new Date()).toISOString();
723306
- writeFileSync88(ikFile, JSON.stringify(ikState, null, 2));
724276
+ writeFileSync87(ikFile, JSON.stringify(ikState, null, 2));
723307
724277
  } catch (ikErr) {
723308
724278
  }
723309
724279
  try {
@@ -723336,7 +724306,7 @@ async function runWithTUI(task, config, repoPath2, callbacks) {
723336
724306
  });
723337
724307
  if (variants.length > 50) variants = variants.slice(-50);
723338
724308
  mkdirSync103(archeDir, { recursive: true });
723339
- writeFileSync88(archeFile, JSON.stringify(variants, null, 2));
724309
+ writeFileSync87(archeFile, JSON.stringify(variants, null, 2));
723340
724310
  } catch {
723341
724311
  }
723342
724312
  }
@@ -723367,7 +724337,7 @@ async function runWithTUI(task, config, repoPath2, callbacks) {
723367
724337
  updated = true;
723368
724338
  }
723369
724339
  if (updated) {
723370
- writeFileSync88(metaFile2, JSON.stringify(store2, null, 2));
724340
+ writeFileSync87(metaFile2, JSON.stringify(store2, null, 2));
723371
724341
  }
723372
724342
  }
723373
724343
  } catch {
@@ -723476,7 +724446,7 @@ Rules:
723476
724446
  });
723477
724447
  if (store2.length > 100) store2 = store2.slice(-100);
723478
724448
  mkdirSync103(metaDir, { recursive: true });
723479
- writeFileSync88(storeFile, JSON.stringify(store2, null, 2));
724449
+ writeFileSync87(storeFile, JSON.stringify(store2, null, 2));
723480
724450
  }
723481
724451
  }
723482
724452
  } catch {
@@ -723526,6 +724496,10 @@ Rules:
723526
724496
  } catch {
723527
724497
  }
723528
724498
  } catch (err) {
724499
+ if (headlessMode) {
724500
+ _apiCallbacks = null;
724501
+ throw err;
724502
+ }
723529
724503
  try {
723530
724504
  const ikFile = join174(repoRoot, ".omnius", "identity", "self-state.json");
723531
724505
  if (existsSync161(ikFile)) {
@@ -723540,7 +724514,7 @@ Rules:
723540
724514
  );
723541
724515
  ikState.session_count = (ikState.session_count || 0) + 1;
723542
724516
  ikState.updated_at = (/* @__PURE__ */ new Date()).toISOString();
723543
- writeFileSync88(ikFile, JSON.stringify(ikState, null, 2));
724517
+ writeFileSync87(ikFile, JSON.stringify(ikState, null, 2));
723544
724518
  }
723545
724519
  const metaFile2 = join174(
723546
724520
  repoRoot,
@@ -723568,7 +724542,7 @@ Rules:
723568
724542
  (item.scores.confidence || 0.5) - 0.02
723569
724543
  );
723570
724544
  }
723571
- writeFileSync88(metaFile2, JSON.stringify(store2, null, 2));
724545
+ writeFileSync87(metaFile2, JSON.stringify(store2, null, 2));
723572
724546
  }
723573
724547
  try {
723574
724548
  const archeDir = join174(repoRoot, ".omnius", "arche");
@@ -723591,7 +724565,7 @@ Rules:
723591
724565
  });
723592
724566
  if (variants.length > 50) variants = variants.slice(-50);
723593
724567
  mkdirSync103(archeDir, { recursive: true });
723594
- writeFileSync88(archeFile, JSON.stringify(variants, null, 2));
724568
+ writeFileSync87(archeFile, JSON.stringify(variants, null, 2));
723595
724569
  } catch {
723596
724570
  }
723597
724571
  } catch {
@@ -723704,7 +724678,7 @@ import { resolve as resolve68 } from "node:path";
723704
724678
  import { spawn as spawn34 } from "node:child_process";
723705
724679
  import {
723706
724680
  mkdirSync as mkdirSync104,
723707
- writeFileSync as writeFileSync89,
724681
+ writeFileSync as writeFileSync88,
723708
724682
  readFileSync as readFileSync132,
723709
724683
  readdirSync as readdirSync58,
723710
724684
  existsSync as existsSync162
@@ -723752,6 +724726,7 @@ async function runJson(task, config, repoPath2) {
723752
724726
  let result;
723753
724727
  const assistantTexts = [];
723754
724728
  const toolCallLog = [];
724729
+ let runnerResult = null;
723755
724730
  try {
723756
724731
  await runWithTUI(task, config, repoPath2, {
723757
724732
  onAssistantText: (text2) => {
@@ -723775,13 +724750,29 @@ async function runJson(task, config, repoPath2) {
723775
724750
  },
723776
724751
  onStatus: (content) => {
723777
724752
  origWrite(JSON.stringify({ type: "status", content }) + "\n");
724753
+ },
724754
+ onRunResult: (runResult) => {
724755
+ runnerResult = {
724756
+ status: runResult.status,
724757
+ completed: runResult.completed,
724758
+ summary: runResult.summary,
724759
+ turns: runResult.turns,
724760
+ toolCalls: runResult.toolCalls,
724761
+ filesEdited: runResult.filesEdited,
724762
+ testsRun: runResult.testsRun
724763
+ };
723778
724764
  }
723779
724765
  });
724766
+ const rr = runnerResult;
723780
724767
  result = {
723781
- status: "completed",
723782
- summary: extractSummary(captured),
724768
+ status: rr?.completed ? "completed" : rr?.status ?? "completed",
724769
+ summary: rr?.summary || extractSummary(captured),
724770
+ turns: rr?.turns,
724771
+ toolCalls: rr?.toolCalls,
724772
+ filesModified: rr?.filesEdited,
724773
+ testsRun: rr?.testsRun,
723783
724774
  durationMs: Date.now() - startTime,
723784
- exitCode: 0
724775
+ exitCode: rr && !rr.completed ? 2 : 0
723785
724776
  };
723786
724777
  } catch (err) {
723787
724778
  result = {
@@ -723805,7 +724796,14 @@ async function runJson(task, config, repoPath2) {
723805
724796
  result.tool_calls = toolCallLog;
723806
724797
  }
723807
724798
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
723808
- if (result.exitCode !== 0) process.exit(1);
724799
+ if (result.exitCode !== 0) process.exit(result.exitCode);
724800
+ if (shouldForceJsonExit()) process.exit(0);
724801
+ }
724802
+ function shouldForceJsonExit() {
724803
+ if (process.env["OMNIUS_JSON_NO_FORCE_EXIT"] === "1") return false;
724804
+ if (process.env["VITEST"] === "true" || process.env["NODE_ENV"] === "test")
724805
+ return false;
724806
+ return true;
723809
724807
  }
723810
724808
  function extractSummary(captured) {
723811
724809
  const all2 = captured.join("");
@@ -723844,7 +724842,7 @@ async function runBackground(task, config, opts) {
723844
724842
  }
723845
724843
  });
723846
724844
  job.pid = child.pid ?? 0;
723847
- writeFileSync89(join175(dir, `${id}.json`), JSON.stringify(job, null, 2));
724845
+ writeFileSync88(join175(dir, `${id}.json`), JSON.stringify(job, null, 2));
723848
724846
  let output = "";
723849
724847
  child.stdout?.on("data", (chunk) => {
723850
724848
  output += chunk.toString();
@@ -723860,7 +724858,7 @@ async function runBackground(task, config, opts) {
723860
724858
  job.summary = result.summary;
723861
724859
  job.durationMs = result.durationMs;
723862
724860
  job.error = result.error;
723863
- writeFileSync89(join175(dir, `${id}.json`), JSON.stringify(job, null, 2));
724861
+ writeFileSync88(join175(dir, `${id}.json`), JSON.stringify(job, null, 2));
723864
724862
  } catch {
723865
724863
  }
723866
724864
  });
@@ -724499,7 +725497,7 @@ __export(eval_exports, {
724499
725497
  evalCommand: () => evalCommand
724500
725498
  });
724501
725499
  import { tmpdir as tmpdir23 } from "node:os";
724502
- import { mkdirSync as mkdirSync105, writeFileSync as writeFileSync90 } from "node:fs";
725500
+ import { mkdirSync as mkdirSync105, writeFileSync as writeFileSync89 } from "node:fs";
724503
725501
  import { join as join177 } from "node:path";
724504
725502
  async function evalCommand(opts, config) {
724505
725503
  const suiteName = opts.suite ?? "basic";
@@ -724631,7 +725629,7 @@ async function evalCommand(opts, config) {
724631
725629
  function createTempEvalRepo() {
724632
725630
  const dir = join177(tmpdir23(), `omnius-eval-${Date.now()}`);
724633
725631
  mkdirSync105(dir, { recursive: true });
724634
- writeFileSync90(
725632
+ writeFileSync89(
724635
725633
  join177(dir, "package.json"),
724636
725634
  JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n",
724637
725635
  "utf8"
@@ -724868,8 +725866,16 @@ function routeCommand(command) {
724868
725866
  }
724869
725867
  function parseCliArgs(argv) {
724870
725868
  const args = argv.slice(2);
725869
+ const parseInput = [...args];
725870
+ const selfTestFlagIndex = parseInput.indexOf("--self-test");
725871
+ if (selfTestFlagIndex >= 0) {
725872
+ const next = parseInput[selfTestFlagIndex + 1];
725873
+ if (!next || next.startsWith("-")) {
725874
+ parseInput.splice(selfTestFlagIndex, 1, "--self-test=crossmodal");
725875
+ }
725876
+ }
724871
725877
  const { values, positionals } = nodeParseArgs2({
724872
- args,
725878
+ args: parseInput,
724873
725879
  options: {
724874
725880
  "dry-run": { type: "boolean" },
724875
725881
  verbose: { type: "boolean", short: "v" },
@@ -724886,6 +725892,7 @@ function parseCliArgs(argv) {
724886
725892
  live: { type: "boolean" },
724887
725893
  json: { type: "boolean", short: "j" },
724888
725894
  background: { type: "boolean" },
725895
+ "self-test": { type: "string" },
724889
725896
  help: { type: "boolean", short: "h" },
724890
725897
  version: { type: "boolean", short: "V" }
724891
725898
  },
@@ -724908,6 +725915,7 @@ function parseCliArgs(argv) {
724908
725915
  local: values.local === true,
724909
725916
  json: values.json === true,
724910
725917
  background: values.background === true,
725918
+ selfTest: typeof values["self-test"] === "string" ? values["self-test"] : void 0,
724911
725919
  help: values.help === true,
724912
725920
  version: values.version === true
724913
725921
  };
@@ -725013,6 +726021,23 @@ Examples:
725013
726021
  `.trim();
725014
726022
  process.stdout.write(text2 + "\n");
725015
726023
  }
726024
+ async function runSelfTest(mode) {
726025
+ if (mode !== "crossmodal") {
726026
+ throw new Error(`Unknown self-test mode: ${mode}`);
726027
+ }
726028
+ process.stdout.write("Running crossmodal smoke tests...\n");
726029
+ const { spawn: spawn35 } = await import("node:child_process");
726030
+ const run2 = (file) => new Promise((resolve71, reject) => {
726031
+ const p2 = spawn35(process.execPath, [file], { stdio: ["ignore", "pipe", "pipe"] });
726032
+ p2.stdout.on("data", (d2) => process.stdout.write(d2));
726033
+ p2.stderr.on("data", (d2) => process.stdout.write(d2));
726034
+ onChildExit(p2, (code8) => code8 === 0 ? resolve71() : reject(new Error(`${file} exited ${code8}`)));
726035
+ });
726036
+ const base3 = process.cwd();
726037
+ await run2(`${base3}/eval/test-crossmodal.mjs`);
726038
+ await run2(`${base3}/eval/test-memory-search.mjs`);
726039
+ process.stdout.write("Self-test complete.\n");
726040
+ }
725016
726041
  async function main() {
725017
726042
  const version4 = getVersion5();
725018
726043
  const parsed = parseCliArgs(process.argv);
@@ -725026,6 +726051,10 @@ async function main() {
725026
726051
  printHelp2(version4);
725027
726052
  return;
725028
726053
  }
726054
+ if (parsed.selfTest) {
726055
+ await runSelfTest(parsed.selfTest);
726056
+ return;
726057
+ }
725029
726058
  if (!parsed.json) {
725030
726059
  const updateInfo = await checkForUpdate(version4);
725031
726060
  if (updateInfo) {
@@ -725044,27 +726073,6 @@ async function main() {
725044
726073
  });
725045
726074
  try {
725046
726075
  switch (parsed.command) {
725047
- case void 0: {
725048
- if (process.argv.includes("--self-test")) {
725049
- const mode = process.argv[process.argv.indexOf("--self-test") + 1] || "crossmodal";
725050
- if (mode === "crossmodal") {
725051
- process.stdout.write("Running crossmodal smoke tests...\n");
725052
- const { spawn: spawn35 } = await import("node:child_process");
725053
- const run2 = (file) => new Promise((resolve71, reject) => {
725054
- const p2 = spawn35(process.execPath, [file], { stdio: ["ignore", "pipe", "pipe"] });
725055
- p2.stdout.on("data", (d2) => process.stdout.write(d2));
725056
- p2.stderr.on("data", (d2) => process.stdout.write(d2));
725057
- onChildExit(p2, (code8) => code8 === 0 ? resolve71() : reject(new Error(`${file} exited ${code8}`)));
725058
- });
725059
- const base3 = process.cwd();
725060
- await run2(`${base3}/eval/test-crossmodal.mjs`);
725061
- await run2(`${base3}/eval/test-memory-search.mjs`);
725062
- process.stdout.write("Self-test complete.\n");
725063
- return;
725064
- }
725065
- }
725066
- break;
725067
- }
725068
726076
  case "run": {
725069
726077
  const { runCommand: runCommand3 } = await Promise.resolve().then(() => (init_run(), run_exports));
725070
726078
  await runCommand3(