omnius 1.0.356 → 1.0.358

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
@@ -33658,7 +33658,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
33658
33658
  }
33659
33659
  const nodeModulesDir = resolve18(this.repoRoot, "node_modules");
33660
33660
  let nexusResolved = false;
33661
- const execAsync3 = (cmd, opts = {}) => new Promise((res, rej) => {
33661
+ const execAsync4 = (cmd, opts = {}) => new Promise((res, rej) => {
33662
33662
  const { exec: ex } = __require("node:child_process");
33663
33663
  ex(cmd, { encoding: "utf8", timeout: opts.timeout ?? 3e4, cwd: opts.cwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
33664
33664
  if (err)
@@ -33673,7 +33673,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
33673
33673
  const failures = [];
33674
33674
  for (const cmd of cmds) {
33675
33675
  try {
33676
- await execAsync3(cmd, { cwd: this.repoRoot, timeout: 18e4 });
33676
+ await execAsync4(cmd, { cwd: this.repoRoot, timeout: 18e4 });
33677
33677
  return `Repaired node-datachannel native addon with: ${cmd.replace(" 2>&1", "")}`;
33678
33678
  } catch (err) {
33679
33679
  failures.push(`${cmd.replace(" 2>&1", "")}: ${err?.message ?? String(err)}`.slice(0, 500));
@@ -33688,7 +33688,7 @@ ${failures.join("\n")}`;
33688
33688
  nexusResolved = true;
33689
33689
  } else {
33690
33690
  try {
33691
- const globalDir2 = await execAsync3("npm root -g", { timeout: 5e3 });
33691
+ const globalDir2 = await execAsync4("npm root -g", { timeout: 5e3 });
33692
33692
  const globalPkg = join31(globalDir2, "open-agents-nexus", "package.json");
33693
33693
  if (existsSync30(globalPkg)) {
33694
33694
  nexusResolved = true;
@@ -33701,13 +33701,13 @@ ${failures.join("\n")}`;
33701
33701
  if (!nexusResolved) {
33702
33702
  const installSpec = `open-agents-nexus@${OPEN_AGENTS_NEXUS_BUNDLED_SPEC}`;
33703
33703
  try {
33704
- await execAsync3(`npm install ${installSpec} 2>&1`, {
33704
+ await execAsync4(`npm install ${installSpec} 2>&1`, {
33705
33705
  cwd: this.repoRoot,
33706
33706
  timeout: 12e4
33707
33707
  });
33708
33708
  } catch {
33709
33709
  try {
33710
- await execAsync3(`npm install -g ${installSpec} 2>&1`, { timeout: 12e4 });
33710
+ await execAsync4(`npm install -g ${installSpec} 2>&1`, { timeout: 12e4 });
33711
33711
  } catch {
33712
33712
  throw new Error(`Failed to install ${installSpec}. Run: npm install ${installSpec}`);
33713
33713
  }
@@ -33752,7 +33752,7 @@ ${failures.join("\n")}`;
33752
33752
  const agentType = args.agent_type || "general";
33753
33753
  const nodePaths = [nodeModulesDir];
33754
33754
  try {
33755
- const globalDir2 = await execAsync3("npm root -g", { timeout: 5e3 });
33755
+ const globalDir2 = await execAsync4("npm root -g", { timeout: 5e3 });
33756
33756
  nodePaths.push(globalDir2);
33757
33757
  } catch {
33758
33758
  }
@@ -98730,12 +98730,12 @@ var require_x509_cjs = __commonJS({
98730
98730
  var require_crypto = __commonJS({
98731
98731
  "../node_modules/acme-client/src/crypto/index.js"(exports) {
98732
98732
  var net5 = __require("net");
98733
- var { promisify: promisify8 } = __require("util");
98733
+ var { promisify: promisify9 } = __require("util");
98734
98734
  var crypto14 = __require("crypto");
98735
98735
  var asn1js4 = require_build2();
98736
98736
  var x5093 = require_x509_cjs();
98737
- var randomInt2 = promisify8(crypto14.randomInt);
98738
- var generateKeyPair2 = promisify8(crypto14.generateKeyPair);
98737
+ var randomInt2 = promisify9(crypto14.randomInt);
98738
+ var generateKeyPair2 = promisify9(crypto14.generateKeyPair);
98739
98739
  x5093.cryptoProvider.set(crypto14.webcrypto);
98740
98740
  var subjectAltNameOID = "2.5.29.17";
98741
98741
  var alpnAcmeIdentifierOID = "1.3.6.1.5.5.7.1.31";
@@ -133580,9 +133580,9 @@ var require_lib = __commonJS({
133580
133580
  var require_forge2 = __commonJS({
133581
133581
  "../node_modules/acme-client/src/crypto/forge.js"(exports) {
133582
133582
  var net5 = __require("net");
133583
- var { promisify: promisify8 } = __require("util");
133583
+ var { promisify: promisify9 } = __require("util");
133584
133584
  var forge = require_lib();
133585
- var generateKeyPair2 = promisify8(forge.pki.rsa.generateKeyPair);
133585
+ var generateKeyPair2 = promisify9(forge.pki.rsa.generateKeyPair);
133586
133586
  function forgeObjectFromPem(input) {
133587
133587
  const msg = forge.pem.decode(input)[0];
133588
133588
  let result;
@@ -150808,7 +150808,7 @@ var require_mock_interceptor = __commonJS({
150808
150808
  var require_mock_client = __commonJS({
150809
150809
  "../node_modules/undici/lib/mock/mock-client.js"(exports, module) {
150810
150810
  "use strict";
150811
- var { promisify: promisify8 } = __require("node:util");
150811
+ var { promisify: promisify9 } = __require("node:util");
150812
150812
  var Client2 = require_client2();
150813
150813
  var { buildMockDispatch } = require_mock_utils();
150814
150814
  var {
@@ -150856,7 +150856,7 @@ var require_mock_client = __commonJS({
150856
150856
  this[kDispatches] = [];
150857
150857
  }
150858
150858
  async [kClose]() {
150859
- await promisify8(this[kOriginalClose])();
150859
+ await promisify9(this[kOriginalClose])();
150860
150860
  this[kConnected] = 0;
150861
150861
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
150862
150862
  }
@@ -151069,7 +151069,7 @@ var require_mock_call_history = __commonJS({
151069
151069
  var require_mock_pool = __commonJS({
151070
151070
  "../node_modules/undici/lib/mock/mock-pool.js"(exports, module) {
151071
151071
  "use strict";
151072
- var { promisify: promisify8 } = __require("node:util");
151072
+ var { promisify: promisify9 } = __require("node:util");
151073
151073
  var Pool = require_pool();
151074
151074
  var { buildMockDispatch } = require_mock_utils();
151075
151075
  var {
@@ -151117,7 +151117,7 @@ var require_mock_pool = __commonJS({
151117
151117
  this[kDispatches] = [];
151118
151118
  }
151119
151119
  async [kClose]() {
151120
- await promisify8(this[kOriginalClose])();
151120
+ await promisify9(this[kOriginalClose])();
151121
151121
  this[kConnected] = 0;
151122
151122
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
151123
151123
  }
@@ -557168,8 +557168,8 @@ var init_verifierRunner = __esm({
557168
557168
  if (patch.testsToRun.length === 0)
557169
557169
  return "(no tests specified)";
557170
557170
  const { execFile: execFile9 } = await import("node:child_process");
557171
- const { promisify: promisify8 } = await import("node:util");
557172
- const execFileAsync6 = promisify8(execFile9);
557171
+ const { promisify: promisify9 } = await import("node:util");
557172
+ const execFileAsync6 = promisify9(execFile9);
557173
557173
  const outputs = [];
557174
557174
  const workDir = this.options.workingDir || repoRoot;
557175
557175
  for (const cmd of patch.testsToRun.slice(0, 3)) {
@@ -566182,8 +566182,8 @@ var init_streaming_executor = __esm({
566182
566182
  startExecution(entry) {
566183
566183
  entry.state = "executing";
566184
566184
  entry.startedAt = Date.now();
566185
- const exec6 = this.executeFn;
566186
- entry.promise = exec6(entry.name, entry.args).then((result) => {
566185
+ const exec7 = this.executeFn;
566186
+ entry.promise = exec7(entry.name, entry.args).then((result) => {
566187
566187
  entry.state = "completed";
566188
566188
  entry.result = result;
566189
566189
  entry.completedAt = Date.now();
@@ -567542,6 +567542,27 @@ var init_completion_resolution_verifier = __esm({
567542
567542
  });
567543
567543
 
567544
567544
  // packages/orchestrator/dist/evidenceBranch.js
567545
+ function buildStructuralPreview2(lines, path12, query) {
567546
+ const n2 = lines.length;
567547
+ const clip3 = (l2) => l2.length > 180 ? l2.slice(0, 180) + "…" : l2;
567548
+ const head = lines.slice(0, HEAD_LINES2).map((l2, i2) => `${i2 + 1}: ${clip3(l2)}`);
567549
+ const isStructural = (l2) => /^\s*(<[A-Za-z!]|#{1,6}\s|def |class |function |export |interface |type |async |public |private |\[[^\]]+\]|[A-Za-z_][\w.]*\s*=)/.test(l2) && l2.trim().length > 0 && l2.trim().length <= 180;
567550
+ const markers = [];
567551
+ for (let i2 = HEAD_LINES2; i2 < n2; i2++) {
567552
+ const l2 = lines[i2];
567553
+ if (isStructural(l2))
567554
+ markers.push(`${i2 + 1}: ${clip3(l2.trim())}`);
567555
+ }
567556
+ const MAX_MARKERS = 30;
567557
+ const sampled = markers.length > MAX_MARKERS ? Array.from({ length: MAX_MARKERS }, (_, k) => markers[Math.floor(k * markers.length / MAX_MARKERS)]) : markers;
567558
+ return [
567559
+ `[STRUCTURAL PREVIEW] "${query}" was not directly located in ${path12} (${n2} lines). Navigate by the structure below and re-read a SPECIFIC region with offset/limit — do NOT re-read the whole file:`,
567560
+ "Head:",
567561
+ ...head,
567562
+ sampled.length ? "Section markers (line: content):" : "(no clear section markers)",
567563
+ ...sampled
567564
+ ].filter(Boolean).join("\n").slice(0, 1600);
567565
+ }
567545
567566
  function queryTerms(query) {
567546
567567
  return [
567547
567568
  ...new Set(query.toLowerCase().replace(/[^a-z0-9_<>./-]+/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS2.has(w)))
@@ -567692,17 +567713,20 @@ async function extractEvidence(opts) {
567692
567713
  const ends = kept.map((s2) => s2.end);
567693
567714
  const snippetLower = claim2.toLowerCase();
567694
567715
  const covered = terms2.filter((t2) => snippetLower.includes(t2)).length;
567695
- return {
567696
- path: path12,
567697
- query,
567698
- claim: claim2,
567699
- sourceStart: starts.length ? Math.min(...starts) : null,
567700
- sourceEnd: ends.length ? Math.max(...ends) : null,
567701
- fileVersion,
567702
- confidence: Math.min(1, covered / Math.max(1, terms2.length)),
567703
- exploredLines: lines.length,
567704
- injectedChars: claim2.length
567705
- };
567716
+ const grepConfidence = Math.min(1, covered / Math.max(1, terms2.length));
567717
+ if (grepConfidence >= EXTRACT_CONFIDENCE_FLOOR) {
567718
+ return {
567719
+ path: path12,
567720
+ query,
567721
+ claim: claim2,
567722
+ sourceStart: starts.length ? Math.min(...starts) : null,
567723
+ sourceEnd: ends.length ? Math.max(...ends) : null,
567724
+ fileVersion,
567725
+ confidence: grepConfidence,
567726
+ exploredLines: lines.length,
567727
+ injectedChars: claim2.length
567728
+ };
567729
+ }
567706
567730
  }
567707
567731
  }
567708
567732
  const windows = lines.length <= WINDOW_LINES * 2 ? [{ start: 1, end: lines.length, text: content, score: 1 }] : selectWindows(lines, terms2);
@@ -567725,7 +567749,7 @@ async function extractEvidence(opts) {
567725
567749
  parsed = null;
567726
567750
  }
567727
567751
  }
567728
- const claim = parsed && parsed.found && parsed.claim ? parsed.claim : `No evidence for "${query}" found in the explored windows of ${path12}.`;
567752
+ const claim = parsed && parsed.found && parsed.claim ? parsed.claim : buildStructuralPreview2(lines, path12, query);
567729
567753
  return {
567730
567754
  path: path12,
567731
567755
  query,
@@ -567743,7 +567767,7 @@ function shouldBranchRead(contentLength, lineCount, hasExplicitSmallRange, thres
567743
567767
  return false;
567744
567768
  return contentLength > thresholdChars || lineCount > 200;
567745
567769
  }
567746
- var WINDOW_LINES, SNIPPET_CONTEXT, HEAD_LINES2, MAX_SNIPPET_LINES, STOPWORDS2;
567770
+ var WINDOW_LINES, SNIPPET_CONTEXT, HEAD_LINES2, MAX_SNIPPET_LINES, EXTRACT_CONFIDENCE_FLOOR, STOPWORDS2;
567747
567771
  var init_evidenceBranch = __esm({
567748
567772
  "packages/orchestrator/dist/evidenceBranch.js"() {
567749
567773
  "use strict";
@@ -567751,6 +567775,7 @@ var init_evidenceBranch = __esm({
567751
567775
  SNIPPET_CONTEXT = 4;
567752
567776
  HEAD_LINES2 = 10;
567753
567777
  MAX_SNIPPET_LINES = 220;
567778
+ EXTRACT_CONFIDENCE_FLOOR = 0.3;
567754
567779
  STOPWORDS2 = /* @__PURE__ */ new Set([
567755
567780
  "the",
567756
567781
  "and",
@@ -615029,35 +615054,105 @@ ${CONTENT_BG_SEQ}`);
615029
615054
  (seq) => seq.endsWith("m") ? seq : ""
615030
615055
  );
615031
615056
  }
615032
- reflowContentLines(livePartialLine, width) {
615057
+ /** Resolve a dynamic-block sentinel line to its registered renderer's lines
615058
+ * at the given width, or null if it is not a (live) sentinel. */
615059
+ dynamicBlockLines(line, maxWidth) {
615060
+ if (!line.startsWith(this.DYNAMIC_BLOCK_MARK_PREFIX) || !line.endsWith(this.DYNAMIC_BLOCK_MARK_SUFFIX)) {
615061
+ return null;
615062
+ }
615063
+ const id = line.slice(
615064
+ this.DYNAMIC_BLOCK_MARK_PREFIX.length,
615065
+ line.length - this.DYNAMIC_BLOCK_MARK_SUFFIX.length
615066
+ );
615067
+ const renderer = this._dynamicBlocks.get(id);
615068
+ if (!renderer) return [];
615069
+ try {
615070
+ return renderer(maxWidth);
615071
+ } catch {
615072
+ return [];
615073
+ }
615074
+ }
615075
+ /** Reflowed-row COUNT for one buffer line at width — dynamic-block aware.
615076
+ * Static lines are a cache-hit `.length` (no allocation), so the count pass
615077
+ * over the whole backlog is cheap. */
615078
+ rowCountForSourceLine(line, maxWidth) {
615079
+ const block = this.dynamicBlockLines(line, maxWidth);
615080
+ if (block !== null) {
615081
+ let n2 = 0;
615082
+ for (const seg of block) n2 += this.reflowContentLine(seg, maxWidth).length;
615083
+ return n2;
615084
+ }
615085
+ return this.reflowContentLine(line, maxWidth).length;
615086
+ }
615087
+ /** Reflowed rows (with bufferIdx) for one buffer line — built ONLY for the
615088
+ * lines actually inside the viewport window. */
615089
+ rowsForSourceLine(line, idx, maxWidth) {
615090
+ const block = this.dynamicBlockLines(line, maxWidth);
615091
+ if (block !== null) {
615092
+ return block.flatMap(
615093
+ (seg) => this.reflowContentLine(seg, maxWidth).map((s2) => ({
615094
+ line: s2,
615095
+ bufferIdx: idx
615096
+ }))
615097
+ );
615098
+ }
615099
+ return this.reflowContentLine(line, maxWidth).map((segment) => ({
615100
+ line: segment,
615101
+ bufferIdx: idx
615102
+ }));
615103
+ }
615104
+ /** Total reflowed row count at width (cheap — cache-hit counts, no big array
615105
+ * allocation). Used for scroll bounds. */
615106
+ reflowedRowCount(livePartialLine, width) {
615033
615107
  const maxWidth = Math.max(16, width);
615034
615108
  const source = livePartialLine ? [...this._contentLines, livePartialLine] : this._contentLines;
615035
- return source.flatMap((line, idx) => {
615036
- if (line.startsWith(this.DYNAMIC_BLOCK_MARK_PREFIX) && line.endsWith(this.DYNAMIC_BLOCK_MARK_SUFFIX)) {
615037
- const id = line.slice(
615038
- this.DYNAMIC_BLOCK_MARK_PREFIX.length,
615039
- line.length - this.DYNAMIC_BLOCK_MARK_SUFFIX.length
615040
- );
615041
- const renderer = this._dynamicBlocks.get(id);
615042
- if (!renderer) return [];
615043
- let blockLines;
615044
- try {
615045
- blockLines = renderer(maxWidth);
615046
- } catch {
615047
- return [];
615048
- }
615049
- return blockLines.flatMap(
615050
- (segment) => this.reflowContentLine(segment, maxWidth).map((s2) => ({
615051
- line: s2,
615052
- bufferIdx: idx
615053
- }))
615054
- );
615055
- }
615056
- return this.reflowContentLine(line, maxWidth).map((segment) => ({
615057
- line: segment,
615058
- bufferIdx: idx
615059
- }));
615060
- });
615109
+ let total = 0;
615110
+ for (let i2 = 0; i2 < source.length; i2++) {
615111
+ total += this.rowCountForSourceLine(source[i2], maxWidth);
615112
+ }
615113
+ return total;
615114
+ }
615115
+ /**
615116
+ * VIRTUALIZED reflow — produce ONLY the viewport window's rows (the visible
615117
+ * `viewportRows` reflowed rows at the given scroll offset). Repaint cost is
615118
+ * O(viewport + dynamic blocks) instead of O(scrollback), so a long session
615119
+ * never re-wraps the whole 10k-line backlog on every paint (the lag-after-
615120
+ * thousands-of-lines stall). Returns the window rows IN ORDER (index 0 = top
615121
+ * visible row), the total row count (for the scrollbar/bounds), and the
615122
+ * clamped scroll offset.
615123
+ */
615124
+ reflowContentWindow(livePartialLine, width, viewportRows, scrollOffset) {
615125
+ const maxWidth = Math.max(16, width);
615126
+ const source = livePartialLine ? [...this._contentLines, livePartialLine] : this._contentLines;
615127
+ const h = Math.max(0, viewportRows);
615128
+ let totalRows = 0;
615129
+ const counts = new Array(source.length);
615130
+ for (let i2 = 0; i2 < source.length; i2++) {
615131
+ const c8 = this.rowCountForSourceLine(source[i2], maxWidth);
615132
+ counts[i2] = c8;
615133
+ totalRows += c8;
615134
+ }
615135
+ const maxOffset = Math.max(0, totalRows - h);
615136
+ const off = scrollOffset < 0 ? 0 : scrollOffset > maxOffset ? maxOffset : scrollOffset;
615137
+ const startIdx = Math.max(0, totalRows - h - off);
615138
+ const endIdx = startIdx + h;
615139
+ const rows = [];
615140
+ let cursor = 0;
615141
+ for (let i2 = 0; i2 < source.length && cursor < endIdx; i2++) {
615142
+ const c8 = counts[i2];
615143
+ const lineStart = cursor;
615144
+ cursor += c8;
615145
+ if (cursor <= startIdx) continue;
615146
+ if (lineStart >= endIdx) break;
615147
+ const lr = this.rowsForSourceLine(source[i2], i2, maxWidth);
615148
+ const from3 = Math.max(0, startIdx - lineStart);
615149
+ const to = Math.min(c8, endIdx - lineStart);
615150
+ for (let k = from3; k < to; k++) {
615151
+ const r2 = lr[k];
615152
+ if (r2) rows.push(r2);
615153
+ }
615154
+ }
615155
+ return { rows, totalRows, startIdx, scrollOffset: off };
615061
615156
  }
615062
615157
  // Memoize per-line reflow: it is a PURE function of (line, width), and
615063
615158
  // reflowContentLines re-wraps the entire scrollback every repaint. Caching
@@ -615201,7 +615296,7 @@ ${CONTENT_BG_SEQ}`);
615201
615296
  maxContentScrollOffset(width = termCols(), livePartialLine = this.getLiveBufferedLine()) {
615202
615297
  return Math.max(
615203
615298
  0,
615204
- this.reflowContentLines(livePartialLine, width).length - this.contentHeight
615299
+ this.reflowedRowCount(livePartialLine, width) - this.contentHeight
615205
615300
  );
615206
615301
  }
615207
615302
  clampContentScrollOffset(width = termCols()) {
@@ -615330,26 +615425,30 @@ ${CONTENT_BG_SEQ}`);
615330
615425
  const w = termCols();
615331
615426
  const _perfOn = process.env["OMNIUS_TUI_PERF"] === "1";
615332
615427
  const _t0 = _perfOn ? performance.now() : 0;
615333
- const reflowedLines = this.reflowContentLines(livePartialLine, w);
615428
+ const win = this.reflowContentWindow(
615429
+ livePartialLine,
615430
+ w,
615431
+ h,
615432
+ this._contentScrollOffset
615433
+ );
615434
+ const reflowedLines = win.rows;
615334
615435
  if (_perfOn) {
615335
615436
  const _ms = performance.now() - _t0;
615336
615437
  if (_ms > 8) {
615337
615438
  try {
615338
615439
  process.stderr.write(
615339
- `[TUI-PERF] reflow ${_ms.toFixed(1)}ms (lines=${reflowedLines.length}, w=${w})
615440
+ `[TUI-PERF] reflow ${_ms.toFixed(1)}ms (window=${win.rows.length}/${win.totalRows} rows, w=${w})
615340
615441
  `
615341
615442
  );
615342
615443
  } catch {
615343
615444
  }
615344
615445
  }
615345
615446
  }
615346
- const totalLines = reflowedLines.length;
615347
- const maxOffset = Math.max(0, totalLines - h);
615348
- if (this._contentScrollOffset > maxOffset) {
615349
- this._contentScrollOffset = maxOffset;
615447
+ if (win.scrollOffset !== this._contentScrollOffset) {
615448
+ this._contentScrollOffset = win.scrollOffset;
615350
615449
  if (this._contentScrollOffset === 0) this._autoScroll = true;
615351
615450
  }
615352
- const startIdx = Math.max(0, totalLines - h - this._contentScrollOffset);
615451
+ const startIdx = 0;
615353
615452
  this._lastPaintReflow = reflowedLines;
615354
615453
  this._lastPaintStartIdx = startIdx;
615355
615454
  const headerSafeFloor = layout().headerBottom + 1;
@@ -615402,8 +615501,8 @@ ${CONTENT_BG_SEQ}`);
615402
615501
  }
615403
615502
  }
615404
615503
  if (this._contentScrollOffset > 0) {
615405
- const linesAbove = startIdx;
615406
- const pct = totalLines > 0 ? Math.round((startIdx + h) / totalLines * 100) : 100;
615504
+ const linesAbove = win.startIdx;
615505
+ const pct = win.totalRows > 0 ? Math.round((win.startIdx + h) / win.totalRows * 100) : 100;
615407
615506
  const indicator = ` ↑ ${linesAbove} lines above · ${pct}% · PgDn/End to return `;
615408
615507
  const pad = Math.max(0, w - indicator.length);
615409
615508
  buf += `\x1B[${this.scrollRegionTop};1H\x1B[7m${indicator}${" ".repeat(pad)}\x1B[0m`;
@@ -647510,9 +647609,9 @@ async function handleUpdate(subcommand, ctx3) {
647510
647609
  }
647511
647610
  };
647512
647611
  }
647513
- const { exec: exec6, spawn: spawn35, execSync: es2 } = await import("node:child_process");
647612
+ const { exec: exec7, spawn: spawn35, execSync: es2 } = await import("node:child_process");
647514
647613
  const execA = (cmd, opts) => new Promise(
647515
- (res, rej) => exec6(
647614
+ (res, rej) => exec7(
647516
647615
  cmd,
647517
647616
  {
647518
647617
  encoding: "utf8",
@@ -648259,7 +648358,7 @@ async function handleUpdate(subcommand, ctx3) {
648259
648358
  installOverlay.setPhase("Native Modules");
648260
648359
  installOverlay.setStatus("Rebuilding native modules...");
648261
648360
  await new Promise((resolve71) => {
648262
- const child = exec6(
648361
+ const child = exec7(
648263
648362
  `${sudoPrefix}npm rebuild -g omnius 2>/dev/null || true`,
648264
648363
  { timeout: 12e4 },
648265
648364
  () => resolve71(true)
@@ -648323,7 +648422,7 @@ async function handleUpdate(subcommand, ctx3) {
648323
648422
  if (fsExists(venvPip2)) {
648324
648423
  installOverlay.setStatus("Upgrading Python packages...");
648325
648424
  await new Promise((resolve71) => {
648326
- const child = exec6(
648425
+ const child = exec7(
648327
648426
  `"${venvPip2}" install --upgrade moondream-station pytesseract Pillow opencv-python-headless numpy 2>/dev/null || true`,
648328
648427
  { timeout: 3e5 },
648329
648428
  (err) => resolve71(!err)
@@ -656183,7 +656282,9 @@ var init_bless_engine = __esm({
656183
656282
  // packages/cli/src/tui/dmn-engine.ts
656184
656283
  import { existsSync as existsSync142, readFileSync as readFileSync115, writeFileSync as writeFileSync74, mkdirSync as mkdirSync86, readdirSync as readdirSync51, unlinkSync as unlinkSync29 } from "node:fs";
656185
656284
  import { join as join154, basename as basename33 } from "node:path";
656186
- function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer) {
656285
+ import { exec as exec6 } from "node:child_process";
656286
+ import { promisify as promisify8 } from "node:util";
656287
+ function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer, projectOpportunities = []) {
656187
656288
  const competenceReport = competence.length > 0 ? competence.map((c8) => {
656188
656289
  const rate = c8.attempts > 0 ? Math.round(c8.successes / c8.attempts * 100) : 0;
656189
656290
  return ` - ${c8.taskType}: ${c8.attempts} attempts, ${rate}% success`;
@@ -656197,11 +656298,16 @@ function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems,
656197
656298
  dueReminders: dueReminders.length > 0 ? dueReminders.map((r2) => ` - ${r2}`).join("\n") : " (none)",
656198
656299
  attentionItems: attentionItems.length > 0 ? attentionItems.map((a2) => ` - ${a2}`).join("\n") : " (none)",
656199
656300
  memoryTopics: memoryTopics.length > 0 ? memoryTopics.map((t2) => ` - ${t2}`).join("\n") : " (none — fresh start)",
656200
- capabilities: capabilities.map((c8) => ` - ${c8}`).join("\n")
656301
+ capabilities: capabilities.map((c8) => ` - ${c8}`).join("\n"),
656302
+ projectOpportunities: projectOpportunities.length > 0 ? projectOpportunities.map((o2) => ` - ${o2}`).join("\n") : " (none detected this scan)"
656201
656303
  });
656202
656304
  } catch {
656305
+ const oppText = projectOpportunities.length > 0 ? `
656306
+
656307
+ Concrete project opportunities (prefer a small, testable capability task):
656308
+ ${projectOpportunities.map((o2) => ` - ${o2}`).join("\n")}` : "";
656203
656309
  return `Gather observations about the agent's recent work:
656204
- ${competenceReport}
656310
+ ${competenceReport}${oppText}
656205
656311
 
656206
656312
  Reflect on what went well and what could improve.`;
656207
656313
  }
@@ -656265,7 +656371,7 @@ function renderDMNResting(consecutiveNulls) {
656265
656371
  process.stdout.write(` ${c3.dim("◌")} DMN resting — ${consecutiveNulls} cycles with nothing to do. Waiting for stimulus...
656266
656372
  `);
656267
656373
  }
656268
- var DMNEngine;
656374
+ var execAsync3, DMNEngine;
656269
656375
  var init_dmn_engine = __esm({
656270
656376
  "packages/cli/src/tui/dmn-engine.ts"() {
656271
656377
  "use strict";
@@ -656276,6 +656382,7 @@ var init_dmn_engine = __esm({
656276
656382
  init_render();
656277
656383
  init_promptLoader3();
656278
656384
  init_tool_adapter();
656385
+ execAsync3 = promisify8(exec6);
656279
656386
  DMNEngine = class {
656280
656387
  constructor(config, repoRoot) {
656281
656388
  this.config = config;
@@ -656293,12 +656400,17 @@ var init_dmn_engine = __esm({
656293
656400
  tasksGenerated: 0,
656294
656401
  consecutiveNulls: 0,
656295
656402
  competence: [],
656296
- reflectionBuffer: []
656403
+ reflectionBuffer: [],
656404
+ recentProposalFingerprints: [],
656405
+ tasksGeneratedToday: 0,
656406
+ dayStamp: ""
656297
656407
  };
656298
656408
  stateDir;
656299
656409
  historyDir;
656300
656410
  /** Recent task summaries — appended by the bless engine after each task */
656301
656411
  recentTaskSummaries = [];
656412
+ /** A: the proposal returned by the most recent runCycle, awaiting its outcome. */
656413
+ _lastProposal = null;
656302
656414
  /** Maximum consecutive null cycles before entering extended rest */
656303
656415
  maxConsecutiveNulls = 3;
656304
656416
  /** TUI writeContent callback — wraps stdout writes in scroll buffer management.
@@ -656326,9 +656438,11 @@ var init_dmn_engine = __esm({
656326
656438
  if (this.recentTaskSummaries.length > 10) {
656327
656439
  this.recentTaskSummaries.shift();
656328
656440
  }
656329
- if (category) {
656330
- this.updateCompetence(category, true);
656441
+ const cat2 = category ?? this._lastProposal?.category;
656442
+ if (cat2) {
656443
+ this.updateCompetence(cat2, true);
656331
656444
  }
656445
+ this._lastProposal = null;
656332
656446
  this.saveState();
656333
656447
  }
656334
656448
  /** Record a task failure for Reflexion-style learning */
@@ -656340,9 +656454,11 @@ var init_dmn_engine = __esm({
656340
656454
  if (this.state.reflectionBuffer.length > 5) {
656341
656455
  this.state.reflectionBuffer.shift();
656342
656456
  }
656343
- if (category) {
656344
- this.updateCompetence(category, false);
656457
+ const cat2 = category ?? this._lastProposal?.category;
656458
+ if (cat2) {
656459
+ this.updateCompetence(cat2, false);
656345
656460
  }
656461
+ this._lastProposal = null;
656346
656462
  this.saveState();
656347
656463
  }
656348
656464
  /** Update competence tracker for a task category */
@@ -656373,10 +656489,11 @@ var init_dmn_engine = __esm({
656373
656489
  this.state.totalCycles++;
656374
656490
  const cycleNum = this.state.totalCycles;
656375
656491
  const startMs = Date.now();
656376
- const [reminders, attention, memoryTopics] = await Promise.all([
656492
+ const [reminders, attention, memoryTopics, projectOpportunities] = await Promise.all([
656377
656493
  this.gatherReminders(),
656378
656494
  this.gatherAttentionItems(),
656379
- this.gatherMemoryTopics()
656495
+ this.gatherMemoryTopics(),
656496
+ this.gatherProjectOpportunities()
656380
656497
  ]);
656381
656498
  const capabilities = [
656382
656499
  "file_read — read files",
@@ -656393,13 +656510,15 @@ var init_dmn_engine = __esm({
656393
656510
  memoryTopics,
656394
656511
  capabilities,
656395
656512
  this.state.competence,
656396
- this.state.reflectionBuffer
656513
+ this.state.reflectionBuffer,
656514
+ projectOpportunities
656397
656515
  );
656398
656516
  const modelTier2 = getModelTier(this.config.model);
656399
656517
  this.write(() => renderDMNCycleStart(cycleNum, modelTier2 === "large"));
656400
656518
  const useDeliberation = modelTier2 === "large";
656401
656519
  let result;
656402
656520
  let deliberationMeta = null;
656521
+ let deliberationFallbackProposals = [];
656403
656522
  if (useDeliberation) {
656404
656523
  onEvent?.({
656405
656524
  type: "status",
@@ -656408,6 +656527,7 @@ var init_dmn_engine = __esm({
656408
656527
  });
656409
656528
  const delib = await this.runDeliberationCycle(prompt, memoryTopics, onEvent);
656410
656529
  result = { summary: delib.summary };
656530
+ deliberationFallbackProposals = delib.workspace.proposals ?? [];
656411
656531
  deliberationMeta = {
656412
656532
  rounds: delib.workspace.rounds,
656413
656533
  evidence: delib.workspace.evidenceAccumulated,
@@ -656418,7 +656538,40 @@ var init_dmn_engine = __esm({
656418
656538
  result = { summary: mono.summary };
656419
656539
  }
656420
656540
  const durationMs = Date.now() - startMs;
656421
- const proposal = this.parseProposal(result.summary);
656541
+ let proposal = this.parseProposal(result.summary, deliberationFallbackProposals);
656542
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
656543
+ if (this.state.dayStamp !== today2) {
656544
+ this.state.dayStamp = today2;
656545
+ this.state.tasksGeneratedToday = 0;
656546
+ }
656547
+ const dailyMax = (() => {
656548
+ const raw = Number(process.env["OMNIUS_DMN_DAILY_TASK_MAX"]);
656549
+ return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 12;
656550
+ })();
656551
+ if (proposal) {
656552
+ const fp = this.fingerprintProposal(proposal);
656553
+ const recent = this.state.recentProposalFingerprints ?? [];
656554
+ if (recent.includes(fp)) {
656555
+ onEvent?.({
656556
+ type: "status",
656557
+ content: "DMN: proposal already proposed recently — resting (dedup)",
656558
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
656559
+ });
656560
+ proposal = null;
656561
+ } else if ((this.state.tasksGeneratedToday ?? 0) >= dailyMax) {
656562
+ onEvent?.({
656563
+ type: "status",
656564
+ content: `DMN: daily task budget reached (${dailyMax}) — resting`,
656565
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
656566
+ });
656567
+ proposal = null;
656568
+ } else {
656569
+ recent.push(fp);
656570
+ if (recent.length > 20) recent.shift();
656571
+ this.state.recentProposalFingerprints = recent;
656572
+ this.state.tasksGeneratedToday = (this.state.tasksGeneratedToday ?? 0) + 1;
656573
+ }
656574
+ }
656422
656575
  const cycleResult = {
656423
656576
  cycleNumber: cycleNum,
656424
656577
  startedAt: new Date(startMs).toISOString(),
@@ -656442,6 +656595,7 @@ var init_dmn_engine = __esm({
656442
656595
  }
656443
656596
  this.state.lastCycleAt = (/* @__PURE__ */ new Date()).toISOString();
656444
656597
  this.saveState();
656598
+ this._lastProposal = proposal;
656445
656599
  return proposal;
656446
656600
  }
656447
656601
  /** Run the DMN's internal LLM agent */
@@ -656705,14 +656859,15 @@ If the Amygdala flagged high urgency (due reminders, critical attention items),
656705
656859
  LOWER the threshold to 0.5 — urgent situations demand faster action even with
656706
656860
  less certainty (urgency-gating model, Cisek et al. 2009).
656707
656861
 
656708
- OUTPUT: Call task_complete with JSON:
656862
+ OUTPUT: Call task_complete with JSON (the JSON object MUST be the FINAL thing you output):
656709
656863
  {
656710
656864
  "decision": "GO" | "NOGO",
656711
656865
  "evidenceScore": 0.X,
656712
656866
  "selectedTask": { task, rationale, provenance, category, confidence, challengeResult } | null,
656713
656867
  "reasoning": "how evidence from each region contributed to the decision",
656714
656868
  "innerVoice": "final self-talk summary — what the agent 'says to itself' before acting"
656715
- }`, onEvent);
656869
+ }
656870
+ If decision is GO, selectedTask MUST be a fully-populated object (NEVER null) — copy the chosen proposal verbatim. A GO with selectedTask:null is invalid.`, onEvent);
656716
656871
  if (gateResult) {
656717
656872
  workspace.findings.push({
656718
656873
  from: "basal_ganglia",
@@ -656862,39 +657017,108 @@ OUTPUT: Call task_complete with JSON:
656862
657017
  };
656863
657018
  }
656864
657019
  /** Parse the DMN agent's output into a structured proposal */
656865
- parseProposal(summary) {
656866
- try {
656867
- const jsonMatch = summary.match(/\{[\s\S]*"selectedTask"[\s\S]*\}/);
656868
- if (!jsonMatch) return null;
656869
- const parsed = JSON.parse(jsonMatch[0]);
656870
- if (!parsed.selectedTask || parsed.selectedTask === null) return null;
656871
- const t2 = parsed.selectedTask;
656872
- return {
656873
- task: String(t2.task ?? ""),
656874
- rationale: String(t2.rationale ?? ""),
656875
- provenance: Array.isArray(t2.provenance) ? t2.provenance.map(String) : [],
656876
- category: ["directive", "exploration", "capability", "maintenance", "social", "autoresearch"].includes(t2.category) ? t2.category : "exploration",
656877
- confidence: typeof t2.confidence === "number" ? Math.min(1, Math.max(0, t2.confidence)) : 0.5,
656878
- challengeResult: t2.challengeResult ? String(t2.challengeResult) : void 0
656879
- };
657020
+ /**
657021
+ * B: layered, GO-aware, markdown-tolerant proposal extraction.
657022
+ *
657023
+ * The basal-ganglia gate emits `{decision, selectedTask, reasoning}` — but in
657024
+ * production it has emitted a GO verdict with `selectedTask:null` or in
657025
+ * markdown ("VERDICT: GO Execute <name>"), which the old single-regex parser
657026
+ * silently dropped to null (wasted deliberation). This recovers the GO target
657027
+ * from the named task or the workspace proposals so a GO is never lost, while
657028
+ * still resting on NOGO / NO_TASK.
657029
+ */
657030
+ parseProposal(summary, fallbackProposals = []) {
657031
+ const coerce2 = (t2) => ({
657032
+ task: String(t2["task"] ?? ""),
657033
+ rationale: String(t2["rationale"] ?? ""),
657034
+ provenance: Array.isArray(t2["provenance"]) ? t2["provenance"].map(String) : [],
657035
+ category: ["directive", "exploration", "capability", "maintenance", "social", "autoresearch"].includes(t2["category"]) ? t2["category"] : "exploration",
657036
+ confidence: typeof t2["confidence"] === "number" ? Math.min(1, Math.max(0, t2["confidence"])) : 0.5,
657037
+ challengeResult: t2["challengeResult"] ? String(t2["challengeResult"]) : void 0
657038
+ });
657039
+ try {
657040
+ const start2 = summary.indexOf("{");
657041
+ const end = summary.lastIndexOf("}");
657042
+ if (start2 >= 0 && end > start2) {
657043
+ const parsed = JSON.parse(summary.slice(start2, end + 1));
657044
+ const decision2 = String(parsed["decision"] ?? "").toUpperCase();
657045
+ const t2 = parsed["selectedTask"];
657046
+ if (decision2 === "NOGO") return null;
657047
+ if (t2 && typeof t2 === "object" && String(t2["task"] ?? "").trim()) return coerce2(t2);
657048
+ }
656880
657049
  } catch {
656881
- if (summary.includes("NO_TASK") || summary.toLowerCase().includes("no task")) {
656882
- return null;
657050
+ }
657051
+ if (/\bNO_?TASK\b/i.test(summary) || /\bNOGO\b/i.test(summary)) return null;
657052
+ const go = /\bVERDICT\b[^\n]*\bGO\b/i.test(summary) || /\bdecision\b[^\n]*\bGO\b/i.test(summary);
657053
+ if (go) {
657054
+ const m2 = summary.match(/Execute[:\s]+[`"']?([A-Za-z0-9_./ -]{3,80})[`"']?/i);
657055
+ const named = m2?.[1]?.trim();
657056
+ if (named) {
657057
+ const lc = named.toLowerCase();
657058
+ const fromWs = fallbackProposals.find(
657059
+ (p2) => p2.task.toLowerCase().includes(lc) || lc.includes(p2.task.toLowerCase().slice(0, 24))
657060
+ );
657061
+ if (fromWs) return fromWs;
657062
+ return coerce2({
657063
+ task: named,
657064
+ rationale: "Recovered from GO verdict (markdown).",
657065
+ provenance: ["dmn:go-verdict-recovery"],
657066
+ category: fallbackProposals[0]?.category ?? "exploration",
657067
+ confidence: 0.55
657068
+ });
656883
657069
  }
656884
- const lines = summary.split("\n").filter((l2) => l2.trim().length > 20);
656885
- if (lines.length > 0) {
656886
- return {
656887
- task: lines[0].trim().slice(0, 500),
656888
- rationale: "Extracted from DMN natural language output",
656889
- provenance: ["dmn:natural-language-extraction"],
656890
- category: "exploration",
656891
- confidence: 0.3
656892
- };
657070
+ if (fallbackProposals.length > 0) {
657071
+ return [...fallbackProposals].sort((a2, b) => b.confidence - a2.confidence)[0];
656893
657072
  }
656894
- return null;
656895
657073
  }
657074
+ return null;
656896
657075
  }
656897
657076
  // ── Context gathering helpers ──────────────────────────────────────────
657077
+ /** Bounded read-only shell — never freezes a cycle (4s timeout, 1MB cap). */
657078
+ async runBounded(cmd) {
657079
+ try {
657080
+ const { stdout } = await execAsync3(cmd, {
657081
+ cwd: this.repoRoot,
657082
+ timeout: 4e3,
657083
+ maxBuffer: 1 << 20
657084
+ });
657085
+ return stdout ?? "";
657086
+ } catch {
657087
+ return "";
657088
+ }
657089
+ }
657090
+ /**
657091
+ * C: scan the actual repo for concrete development opportunities so the DMN
657092
+ * proposes grounded capability work (implement a plan, fix a TODO, harden a
657093
+ * recently-changed module) rather than abstract infra. All signals are cheap
657094
+ * and bounded — never runs a heavy test suite here.
657095
+ */
657096
+ async gatherProjectOpportunities() {
657097
+ const out = [];
657098
+ const root = this.repoRoot;
657099
+ try {
657100
+ const plansDir = join154(root, ".aiwg", "plans");
657101
+ if (existsSync142(plansDir)) {
657102
+ for (const f2 of readdirSync51(plansDir).filter((p2) => p2.endsWith(".md")).slice(0, 8)) {
657103
+ out.push(`Plan backlog: implement .aiwg/plans/${f2}`);
657104
+ }
657105
+ }
657106
+ const todos = await this.runBounded(
657107
+ `rg -n --no-heading -e "TODO|FIXME" -g "*.ts" -g "*.py" packages src 2>/dev/null | head -12`
657108
+ );
657109
+ for (const line of todos.split("\n").filter(Boolean).slice(0, 8)) {
657110
+ out.push(`Code gap: ${line.slice(0, 160)}`);
657111
+ }
657112
+ const churn = await this.runBounded(
657113
+ `git diff --name-only HEAD~5 2>/dev/null | head -10`
657114
+ );
657115
+ for (const f2 of churn.split("\n").filter(Boolean).slice(0, 6)) {
657116
+ out.push(`Recently changed (consider hardening/tests): ${f2}`);
657117
+ }
657118
+ } catch {
657119
+ }
657120
+ return [...new Set(out)].slice(0, 14);
657121
+ }
656898
657122
  async gatherReminders() {
656899
657123
  try {
656900
657124
  const reminders = await getDueReminders(this.repoRoot);
@@ -656997,6 +657221,16 @@ OUTPUT: Call task_complete with JSON:
656997
657221
  }
656998
657222
  }
656999
657223
  }
657224
+ /** F1: stable fingerprint of a proposal for dedup (category + task + rationale). */
657225
+ fingerprintProposal(p2) {
657226
+ const normalized = cleanForStorage(`${p2.category} ${p2.task} ${p2.rationale}`).toLowerCase().replace(/\d+/g, "#").replace(/\s+/g, " ").trim().slice(0, 600);
657227
+ let hash = 2166136261;
657228
+ for (let i2 = 0; i2 < normalized.length; i2++) {
657229
+ hash ^= normalized.charCodeAt(i2);
657230
+ hash = Math.imul(hash, 16777619);
657231
+ }
657232
+ return (hash >>> 0).toString(16);
657233
+ }
657000
657234
  fingerprintCycle(result) {
657001
657235
  const selected = result.selectedTask ? [
657002
657236
  result.selectedTask.category,
@@ -662172,18 +662406,27 @@ function deriveVisualEvidencePlan(request) {
662172
662406
  const needsText = /\b(text|read|ocr|extract|label|word|number|what does it say|transcript|character|letter|digit|spell|transcribe|copy|quote|type|what is written)\b/i.test(prompt);
662173
662407
  const needsScene = /\b(what|who|where|describe|scene|object|person|identify|tell me about|explain|see|show|happening|look like|recogniz)\b/i.test(prompt) && !needsText;
662174
662408
  const needsUI = /\b(ui|button|menu|dialog|window|interface|screen|dashboard|form|field|input|select|option|dropdown)\b/i.test(prompt);
662409
+ const comprehensive = (reason) => ({
662410
+ stages: [
662411
+ { kind: "low_fidelity_observation", required: false },
662412
+ { kind: "ocr", required: true },
662413
+ { kind: "auxiliary_vision", required: true }
662414
+ ],
662415
+ reason
662416
+ });
662175
662417
  switch (detail) {
662176
662418
  case "low":
662177
- return { stages: [{ kind: "low_fidelity_observation", required: true }], reason: "low detail requested" };
662419
+ return { stages: [{ kind: "low_fidelity_observation", required: true }], reason: "low detail explicitly requested" };
662178
662420
  case "text":
662179
- return { stages: [{ kind: "low_fidelity_observation", required: false }, { kind: "ocr", required: true }, { kind: "auxiliary_vision", required: false }], reason: "text extraction requested" };
662421
+ return comprehensive("text extraction requested full vision still runs for classification");
662422
+ case "visual":
662423
+ return comprehensive("visual analysis requested");
662180
662424
  case "full":
662181
- return { stages: [{ kind: "low_fidelity_observation", required: false }, { kind: "ocr", required: needsText || needsUI }, { kind: "auxiliary_vision", required: true }], reason: "full detail requested" };
662425
+ return comprehensive("full detail requested");
662182
662426
  default:
662183
- const stages = [{ kind: "low_fidelity_observation", required: false }];
662184
- if (needsText || needsUI) stages.push({ kind: "ocr", required: true });
662185
- if (needsScene || needsUI) stages.push({ kind: "auxiliary_vision", required: !needsText && !needsUI });
662186
- return { stages, reason: needsText ? "text evidence needed" : needsScene ? "scene analysis needed" : needsUI ? "UI/document analysis needed" : "auto" };
662427
+ return comprehensive(
662428
+ needsText ? "comprehensive (text emphasis)" : needsScene ? "comprehensive (scene emphasis)" : needsUI ? "comprehensive (UI/document emphasis)" : "comprehensive (full vision + OCR on all media)"
662429
+ );
662187
662430
  }
662188
662431
  }
662189
662432
  async function executeVisualEvidencePlan(resolution, plan, executor) {
@@ -662227,6 +662470,7 @@ __export(vision_ingress_exports, {
662227
662470
  isTesseractAvailable: () => isTesseractAvailable,
662228
662471
  isVisionModel: () => isVisionModel,
662229
662472
  queryVisionModel: () => queryVisionModel,
662473
+ resolveVisionModel: () => resolveVisionModel,
662230
662474
  runVisionIngress: () => runVisionIngress
662231
662475
  });
662232
662476
  import { execFileSync as execFileSync10 } from "node:child_process";
@@ -662355,25 +662599,32 @@ async function queryVisionModel(modelName, imagePath, prompt = "Describe what yo
662355
662599
  return "";
662356
662600
  }
662357
662601
  }
662602
+ function resolveVisionModel(currentModel) {
662603
+ if (currentModel && isVisionModel(currentModel)) return currentModel;
662604
+ const env2 = (process.env["OMNIUS_VISION_MODEL"] || "").trim();
662605
+ if (env2) return env2;
662606
+ return "moondream";
662607
+ }
662358
662608
  async function runVisionIngress(image, currentModel) {
662359
662609
  const ocrText = advancedOcr(image.path);
662360
662610
  let visionDescription = "";
662361
662611
  let visionUsed = false;
662362
- if (currentModel && isVisionModel(currentModel)) {
662363
- visionDescription = await queryVisionModel(currentModel, image.path);
662612
+ const visionModel = resolveVisionModel(currentModel);
662613
+ if (visionModel) {
662614
+ visionDescription = await queryVisionModel(visionModel, image.path);
662364
662615
  visionUsed = visionDescription.length > 0;
662365
662616
  }
662366
662617
  const parts = [];
662367
662618
  if (ocrText.length > 0) {
662368
- parts.push(`[OCR Text from pasted image]
662619
+ parts.push(`[OCR Text from image]
662369
662620
  ${ocrText}`);
662370
662621
  }
662371
662622
  if (visionDescription.length > 0) {
662372
- parts.push(`[Vision analysis of pasted image (model: ${currentModel})]
662623
+ parts.push(`[Vision analysis of image (model: ${visionModel})]
662373
662624
  ${visionDescription}`);
662374
662625
  }
662375
662626
  if (parts.length === 0) {
662376
- parts.push(`[Image pasted at ${image.path} — no text detected by OCR, no vision model available for analysis]`);
662627
+ parts.push(`[Image at ${image.path} — OCR found no text and the vision model (${visionModel}) returned no description; treat as UNCOMPREHENDED and re-run telegram_image_analyze with detail='full' before answering.]`);
662377
662628
  }
662378
662629
  const contextBlock = parts.join("\n\n");
662379
662630
  return {
@@ -665062,7 +665313,7 @@ Public Telegram vision and media stack
665062
665313
 
665063
665314
  Public Telegram runs have the full scoped media-analysis stack for media posted in this chat:
665064
665315
  - Use telegram_media_recent to find recent scoped media, then use path/media aliases 'reply' and 'latest' instead of exposing local paths to users.
665065
- - For image questions, prefer telegram_image_analyze first. It resolves omitted/reply/latest media, starts with low-fidelity image intake, uses basic OCR as the text extraction probe, escalates to advanced OCR when text is dense or under-extracted, and escalates to Moondream vision when visual QA/captioning is needed.
665316
+ - MANDATORY: whenever one or more images are present (posted, replied-to, or recent), run a FULL comprehension pass on EVERY image BEFORE responding — telegram_image_analyze with detail='full' (advanced OCR + Moondream vision) on each; if a burst of images was posted, enumerate them with telegram_media_recent and analyze ALL of them. Base the answer ONLY on the extracted content (objects, scene, any text). NEVER answer from metadata alone (count, size, timestamp, caption), never claim you can't say what's pictured without running vision, and never offer to analyze only "the ones you care about" — full vision on all of them IS the job. Do not stop until every image is fully comprehended.
665066
665317
  - Use ocr for quick image text extraction, ocr_image_advanced when basic OCR shows dense or degraded text, image_read for image metadata + multimodal image payload, and vision for direct Moondream captioning, visual QA, object detection, or pointing.
665067
665318
  - Use pdf_to_text for embedded-text PDFs and ocr_pdf for scanned PDFs.
665068
665319
  - Use video_understand and transcribe_file for video/audio media posted in this chat.
@@ -675775,7 +676026,7 @@ ${currentTelegramPrompt}`;
675775
676026
  TELEGRAM_LINK_INTEGRITY_CONTRACT,
675776
676027
  "If a user explicitly states a durable preference for reply cadence/order, call telegram_preference_set. Do not infer or classify reply-mode preferences from keywords, style, tone, or task type.",
675777
676028
  TELEGRAM_EVIDENCE_SUFFICIENCY_CONTRACT,
675778
- "You have the full scoped Telegram media-analysis stack by default: telegram_image_analyze, telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. For image questions, prefer telegram_image_analyze first; it resolves omitted/reply/latest media, starts with low-fidelity image intake, uses basic OCR as the text extraction probe, escalates to advanced OCR when text is dense or under-extracted, and escalates to Moondream vision when visual QA/captioning is needed.",
676029
+ "You have the full scoped Telegram media-analysis stack by default: telegram_image_analyze, telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. MANDATORY image handling: whenever one or more images are present in the message (or a referenced/recent message), you MUST run a FULL comprehension pass on EVERY image before you respond — call telegram_image_analyze with detail='full' (advanced OCR + Moondream vision) on each, and if multiple images were sent in a burst, analyze ALL of them (use telegram_media_recent to enumerate them). Base your answer ONLY on the actual extracted content (objects, scene, and any text). NEVER answer from metadata alone (count, file size, timestamp, caption) and NEVER say you 'can't say what's pictured without running vision' or offer to analyze 'the ones you care about' — running full vision on all of them IS your job. Do not stop until every image is fully comprehended; if a pass returns nothing, retry with detail='full' or image_read+ocr_image_advanced+vision before concluding.",
675779
676030
  formatIdentityMemoryContext(chatLabel || "Telegram private chat"),
675780
676031
  reminderToolContract,
675781
676032
  "If the user asks you to create an image, audio file, video, 3D/CAD model, or document artifact, create it with the scoped creative tools. Freshly generated artifacts are recorded and automatically attached to this Telegram chat when the turn completes, so do not call telegram_send_file for those same artifacts unless the user asked for a specific caption, existing/unrecorded file, or non-default target.",
@@ -679615,7 +679866,7 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
679615
679866
  * Downloads the file, runs it through the appropriate pipeline,
679616
679867
  * caches it, and returns a text description for the agent.
679617
679868
  */
679618
- async processMedia(msg, source = "message") {
679869
+ async processMedia(msg, source = "message", eager = false) {
679619
679870
  const media = source === "reply" ? msg.replyToMedia : msg.media;
679620
679871
  if (!media) return "";
679621
679872
  const { type, fileId, fileUniqueId, mimeType, caption } = media;
@@ -679624,6 +679875,12 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
679624
679875
  const sourceLabel = source === "reply" ? "replied-to " : "";
679625
679876
  const mediaAlias = sourceMessageId ? `message_id:${sourceMessageId}` : source === "reply" ? "reply" : "latest";
679626
679877
  const safeCaption = caption ? ` — caption: ${telegramContextJsonString(caption, 220)}` : "";
679878
+ const cacheKey = `${String(msg.chatId)}:${String(sourceMessageId ?? 0)}:${fileUniqueId}`;
679879
+ const existingEntry = this.mediaCache.get(cacheKey);
679880
+ if (existingEntry && existsSync146(existingEntry.localPath)) {
679881
+ existingEntry.cachedAt = Date.now();
679882
+ return existingEntry.extractedContent || `[${sourceLabel}${type} received: path_alias=${mediaAlias}${safeCaption}]`;
679883
+ }
679627
679884
  let ext = ".bin";
679628
679885
  if (isImageMedia) ext = telegramImageExtension(media);
679629
679886
  else if (type === "audio" || type === "voice") ext = ".ogg";
@@ -679647,10 +679904,7 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
679647
679904
  caption,
679648
679905
  cachedAt: Date.now()
679649
679906
  };
679650
- this.mediaCache.set(
679651
- `${String(msg.chatId)}:${String(sourceMessageId ?? 0)}:${fileUniqueId}`,
679652
- cacheEntry
679653
- );
679907
+ this.mediaCache.set(cacheKey, cacheEntry);
679654
679908
  const metadataKey = String(msg.chatId);
679655
679909
  if (!this.mediaMetadata.has(metadataKey)) {
679656
679910
  this.mediaMetadata.set(metadataKey, []);
@@ -679663,7 +679917,7 @@ ${knownList}` : "Private-user telegram_send_file target must be this DM or a kno
679663
679917
  username: msg.username
679664
679918
  });
679665
679919
  let description = `[${type}${caption ? `: ${caption}` : ""}]`;
679666
- if (isImageMedia) {
679920
+ if (isImageMedia && !eager) {
679667
679921
  let visionContext = "";
679668
679922
  try {
679669
679923
  const { runVisionIngress: runVisionIngress2, formatImageContextPrefix: formatImageContextPrefix2 } = await Promise.resolve().then(() => (init_vision_ingress(), vision_ingress_exports));
@@ -680762,6 +681016,10 @@ ${caption}\r
680762
681016
  if (this.adminUserId && !this.agentConfig) {
680763
681017
  if (!isAdmin) continue;
680764
681018
  }
681019
+ if (msg.media) {
681020
+ void this.processMedia(msg, "message", true).catch(() => {
681021
+ });
681022
+ }
680765
681023
  if (this.agentConfig && this.repoRoot) {
680766
681024
  this.handleMessageWithSubAgent(msg).catch((err) => {
680767
681025
  this.tuiWrite(
@@ -712169,10 +712427,10 @@ ${incompleteList}${more}
712169
712427
  const scripts = pkg.scripts || {};
712170
712428
  const checkScript = scripts["typecheck"] ? "typecheck" : scripts["build"] ? "build" : null;
712171
712429
  if (checkScript) {
712172
- const { exec: exec6 } = await import("node:child_process");
712173
- const { promisify: promisify8 } = await import("node:util");
712430
+ const { exec: exec7 } = await import("node:child_process");
712431
+ const { promisify: promisify9 } = await import("node:util");
712174
712432
  try {
712175
- await promisify8(exec6)(`npm run ${checkScript} --silent 2>&1`, {
712433
+ await promisify9(exec7)(`npm run ${checkScript} --silent 2>&1`, {
712176
712434
  cwd: cwd4,
712177
712435
  timeout: 12e4,
712178
712436
  encoding: "utf-8",
@@ -713374,6 +713632,10 @@ Meta-critique: quality ${meta.quality}/5, thorough: ${meta.thorough}`;
713374
713632
  }
713375
713633
  };
713376
713634
  }
713635
+ function dmnDevDiscipline(category) {
713636
+ if (category !== "capability") return "";
713637
+ return "\n\nYou are acting as a self-directed developer. Work on a NEW git branch. Implement the change, then make the project's tests pass for what you touched. Do NOT call task_complete until the change is implemented AND its tests pass AND the result is evidenced (the completion gates will block an unverified claim). If the change is not feasible, stop with a BLOCKED: summary explaining why.";
713638
+ }
713377
713639
  function gatherMemorySnippets(root) {
713378
713640
  const snippets = [];
713379
713641
  const dirs = [
@@ -713482,36 +713744,28 @@ function buildNewTaskIntakePacket(ingress, interpretation, previousPrompt, previ
713482
713744
  function createDMNEventHandler(verbose, writeContent) {
713483
713745
  return (event) => {
713484
713746
  switch (event.type) {
713747
+ // DMN tool activity renders in the SAME unicode boxes + format as regular
713748
+ // (main-loop) tool calls for consistent observability — the verbose flag
713749
+ // controls detail exactly as in the main loop, not whether the box shows.
713750
+ // The DMN remains automatic/self-directed; only its rendering is unified.
713485
713751
  case "tool_call":
713486
- if (verbose) {
713487
- writeContent(() => {
713488
- const args = event.toolArgs ?? {};
713489
- renderToolCallStart(event.toolName ?? "unknown", args, true);
713490
- });
713491
- } else {
713492
- const argSummary = formatDMNToolArgs(
713493
- event.toolName ?? "",
713494
- event.toolArgs ?? {}
713495
- );
713496
- writeContent(
713497
- () => process.stdout.write(
713498
- ` ${c3.dim(`DMN → ${event.toolName}`)}${argSummary ? c3.dim(`: ${argSummary}`) : ""}
713499
- `
713500
- )
713752
+ writeContent(() => {
713753
+ renderToolCallStart(
713754
+ event.toolName ?? "unknown",
713755
+ event.toolArgs ?? {},
713756
+ verbose
713501
713757
  );
713502
- }
713758
+ });
713503
713759
  break;
713504
713760
  case "tool_result":
713505
- if (verbose) {
713506
- writeContent(() => {
713507
- renderToolResult(
713508
- event.toolName ?? "unknown",
713509
- event.success ?? false,
713510
- event.content ?? "",
713511
- true
713512
- );
713513
- });
713514
- }
713761
+ writeContent(() => {
713762
+ renderToolResult(
713763
+ event.toolName ?? "unknown",
713764
+ event.success ?? false,
713765
+ event.content ?? "",
713766
+ verbose
713767
+ );
713768
+ });
713515
713769
  break;
713516
713770
  case "model_response":
713517
713771
  if (verbose && event.content) {
@@ -713548,27 +713802,6 @@ ${event.content}`
713548
713802
  }
713549
713803
  };
713550
713804
  }
713551
- function formatDMNToolArgs(toolName, args) {
713552
- switch (toolName) {
713553
- case "memory_read":
713554
- case "memory_search":
713555
- return String(args["topic"] ?? args["query"] ?? "").slice(0, 60);
713556
- case "file_read":
713557
- return String(args["path"] ?? "").slice(0, 80);
713558
- case "list_directory":
713559
- return String(args["path"] ?? ".").slice(0, 80);
713560
- case "shell":
713561
- return String(args["command"] ?? "").slice(0, 80);
713562
- case "memory_write":
713563
- return `${args["topic"] ?? ""}.${args["key"] ?? ""}`;
713564
- case "grep_search":
713565
- return String(args["pattern"] ?? "").slice(0, 60);
713566
- case "find_files":
713567
- return String(args["pattern"] ?? "").slice(0, 60);
713568
- default:
713569
- return "";
713570
- }
713571
- }
713572
713805
  function truncateByLines(text2, maxLines, maxChars = 600) {
713573
713806
  const lines = text2.split("\n");
713574
713807
  if (lines.length <= maxLines && text2.length <= maxChars) return text2;
@@ -715499,7 +715732,8 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
715499
715732
  turns: result.turns,
715500
715733
  toolCalls: result.toolCalls,
715501
715734
  durationMs: result.durationMs,
715502
- model: config.model
715735
+ model: config.model,
715736
+ verified: result.completed === true
715503
715737
  });
715504
715738
  _tasksSinceImprove++;
715505
715739
  if (_tasksSinceImprove >= SELF_IMPROVE_INTERVAL) {
@@ -716760,6 +716994,7 @@ ${result.summary}`
716760
716994
  let dreamEngine = null;
716761
716995
  let blessEngine = null;
716762
716996
  let dmnEngine = null;
716997
+ let dmnOriginatedTask = false;
716763
716998
  const snrEngine = new SNREngine(currentConfig, repoRoot);
716764
716999
  const emotionEngine = new EmotionEngine({
716765
717000
  backendUrl: currentConfig.backendUrl,
@@ -716853,7 +717088,8 @@ Respond concisely and safely.`;
716853
717088
 
716854
717089
  ${proposal.task}
716855
717090
 
716856
- Rationale: ${proposal.rationale}${provenanceNote}`;
717091
+ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.category)}`;
717092
+ dmnOriginatedTask = true;
716857
717093
  writeContent(
716858
717094
  () => renderInfo(
716859
717095
  `DMN auto-cycle: ${proposal.category} task (${Math.round(proposal.confidence * 100)}% confidence)`
@@ -721407,8 +721643,8 @@ ${taskInput}`;
721407
721643
  const updateInfo = await checkForUpdate(version4);
721408
721644
  if (updateInfo) {
721409
721645
  _autoUpdatedThisSession = true;
721410
- const { exec: exec6 } = await import("node:child_process");
721411
- exec6(
721646
+ const { exec: exec7 } = await import("node:child_process");
721647
+ exec7(
721412
721648
  `npm install -g omnius@latest --prefer-online`,
721413
721649
  { timeout: 18e4 },
721414
721650
  (err) => {
@@ -721465,7 +721701,18 @@ ${emotion.emoji} ${emotion.label}`);
721465
721701
  }
721466
721702
  if (blessEngine?.isActive) {
721467
721703
  blessEngine.recordTaskComplete();
721468
- if (dmnEngine && lastCompletedSummary) {
721704
+ if (dmnEngine && dmnOriginatedTask && lastCompletedSummary) {
721705
+ const verified = lastTaskMeta?.verified === true;
721706
+ if (verified) {
721707
+ dmnEngine.recordTaskCompletion(lastCompletedSummary);
721708
+ } else {
721709
+ dmnEngine.recordTaskFailure(
721710
+ lastCompletedSummary,
721711
+ "run did not pass the completion/resolution gates (unverified)"
721712
+ );
721713
+ }
721714
+ dmnOriginatedTask = false;
721715
+ } else if (dmnEngine && lastCompletedSummary) {
721469
721716
  dmnEngine.recordTaskCompletion(lastCompletedSummary);
721470
721717
  }
721471
721718
  const nextTask = await blessEngine.getNextTask();
@@ -721510,7 +721757,8 @@ Respond concisely and safely.`;
721510
721757
 
721511
721758
  ${proposal.task}
721512
721759
 
721513
- Rationale: ${proposal.rationale}${provenanceNote}`;
721760
+ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.category)}`;
721761
+ dmnOriginatedTask = true;
721514
721762
  writeContent(
721515
721763
  () => renderInfo(
721516
721764
  `DMN auto-cycle: ${proposal.category} task (${Math.round(proposal.confidence * 100)}% confidence)`
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.356",
3
+ "version": "1.0.358",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.356",
9
+ "version": "1.0.358",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
@@ -5354,9 +5354,9 @@
5354
5354
  }
5355
5355
  },
5356
5356
  "node_modules/node-addon-api": {
5357
- "version": "8.8.0",
5358
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz",
5359
- "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==",
5357
+ "version": "8.9.0",
5358
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz",
5359
+ "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==",
5360
5360
  "license": "MIT",
5361
5361
  "optional": true,
5362
5362
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.356",
3
+ "version": "1.0.358",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,6 +45,9 @@ Known memory topics:
45
45
  Available capabilities:
46
46
  {{capabilities}}
47
47
 
48
+ Concrete project opportunities (grounded in THIS repo — open plans, TODO/FIXME, recently-changed files):
49
+ {{projectOpportunities}}
50
+
48
51
  ═══════════════════════════════════════════════════════════════════════════
49
52
 
50
53
  PHASE 2: REFLECT, CONSOLIDATE & REASON (Generative Agents + Reflexion)
@@ -75,11 +78,23 @@ propose tasks at the FRONTIER of current capabilities — neither too easy
75
78
  (already mastered) nor too hard (no chance of success). Check the competence
76
79
  tracker to calibrate difficulty.
77
80
 
81
+ PREFER DEVELOPMENT. You are a self-directed developer of THIS project. When
82
+ concrete project opportunities exist above, PREFER a `capability` task that
83
+ implements or hardens one of them (a small, shippable, testable improvement to
84
+ this codebase) over abstract exploration/infra. Quote the specific
85
+ file/plan/TODO in `provenance`.
86
+
87
+ Curriculum progression (Voyager Goldilocks, driven by the competence tracker):
88
+ - If a category's success rate is HIGH (≥70% over ≥3 attempts), propose a
89
+ LARGER / harder task in it — escalate scope.
90
+ - If LOW (<40%), pick a SMALLER-scoped task or a different category — back off.
91
+ - NEVER repeat a task that recently failed (see the Reflexion buffer above).
92
+
78
93
  For each candidate, specify:
79
94
  - The task description (specific, actionable, measurable)
80
95
  - Rationale (why this task, what led you to it)
81
96
  - Provenance (which memories, directives, or signals informed this)
82
- - Category: directive | exploration | capability | maintenance | social
97
+ - Category: directive | exploration | capability | maintenance | social | autoresearch
83
98
  - Confidence (0-1, calibrated against competence data)
84
99
 
85
100
  ═══════════════════════════════════════════════════════════════════════════