omnius 1.0.346 → 1.0.348

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
@@ -547778,9 +547778,9 @@ function _findNemotronScript() {
547778
547778
  return null;
547779
547779
  }
547780
547780
  function _wordSimilarity(a2, b) {
547781
- const tokenize6 = (s2) => new Set(s2.toLowerCase().replace(/[^a-z0-9\s']/g, " ").split(/\s+/).filter(Boolean));
547782
- const sa = tokenize6(a2);
547783
- const sb = tokenize6(b);
547781
+ const tokenize7 = (s2) => new Set(s2.toLowerCase().replace(/[^a-z0-9\s']/g, " ").split(/\s+/).filter(Boolean));
547782
+ const sa = tokenize7(a2);
547783
+ const sb = tokenize7(b);
547784
547784
  if (sa.size === 0 && sb.size === 0)
547785
547785
  return 1;
547786
547786
  let inter = 0;
@@ -567219,7 +567219,7 @@ function adversarySystemPrompt() {
567219
567219
  "",
567220
567220
  "Given the agent's latest message and recent tool outcomes, decide whether the claim is proven.",
567221
567221
  "Respond with ONLY a JSON object, no prose, no code fences:",
567222
- '{"class":"false_success|unproven_claim|weak_evidence|false_failure|ok",',
567222
+ '{"class":"false_success|unproven_claim|weak_evidence|false_failure|repeated_action|ok",',
567223
567223
  ' "shortText":"<=12 word headline",',
567224
567224
  ' "confidence":0.0-1.0, // how strongly the claim is NOT proven',
567225
567225
  ' "details":"2-4 sentence skeptical critique citing the specific gap",',
@@ -567232,6 +567232,24 @@ function adversarySystemPrompt() {
567232
567232
  function buildObservationPrompt(obs, recentLedger) {
567233
567233
  const outcomes = obs.recentToolOutcomes.slice(-8).map((o2) => ` - ${o2.tool}: ${o2.succeeded ? "OK" : "FAIL"} — ${o2.preview.slice(0, 120)}`).join("\n");
567234
567234
  const priorDoubts = recentLedger.slice(-3).filter((e2) => e2.verdict !== "ok").map((e2) => ` - turn ${e2.turn}: ${e2.verdict} — demanded: ${e2.demand}`).join("\n");
567235
+ if (obs.loopSignal) {
567236
+ const ls2 = obs.loopSignal;
567237
+ return [
567238
+ `The agent has repeated the SAME action ${ls2.count}× this run: ${ls2.tool} on ${ls2.target}.`,
567239
+ `This is a loop. Reason about WHY — for THIS specific call, not generically.`,
567240
+ ls2.alreadyHave ? `Evidence the agent ALREADY obtained from a prior identical call:
567241
+ ${ls2.alreadyHave.slice(0, 900)}` : `(No cached result available for the repeated call.)`,
567242
+ "",
567243
+ "Agent's latest message:",
567244
+ obs.assistantText.slice(0, 1200) || "(empty)",
567245
+ "",
567246
+ "Recent tool outcomes:",
567247
+ outcomes || " (none)",
567248
+ "",
567249
+ `Decide: does the agent already HAVE what this repeated call would return (class "repeated_action"), and if so what should it do INSTEAD? Be concrete — cite the specific content above, name the next action. If the repeat is actually justified (state genuinely changed), say class "ok".`,
567250
+ "Return ONLY the JSON object."
567251
+ ].join("\n");
567252
+ }
567235
567253
  return [
567236
567254
  obs.claimsCompletion ? "The agent is asserting COMPLETION this turn." : "The agent produced a progress/success-flavored claim this turn.",
567237
567255
  "",
@@ -567263,6 +567281,7 @@ function parseAdversaryCritique(raw) {
567263
567281
  }
567264
567282
  const cls = String(obj["class"] ?? "").toLowerCase();
567265
567283
  const valid = [
567284
+ "repeated_action",
567266
567285
  "false_success",
567267
567286
  "unproven_claim",
567268
567287
  "weak_evidence",
@@ -567316,17 +567335,22 @@ var init_adversaryStream = __esm({
567316
567335
  shouldAudit(obs) {
567317
567336
  if (obs.claimsCompletion)
567318
567337
  return true;
567338
+ if (obs.loopSignal)
567339
+ return true;
567319
567340
  return SUCCESS_LANGUAGE.test(obs.assistantText);
567320
567341
  }
567321
567342
  /** Ingest an observation. Replaces any prior un-audited pending observation. */
567322
567343
  observe(obs) {
567323
567344
  if (!this.shouldAudit(obs))
567324
567345
  return;
567325
- const sig = `${obs.turn}:${obs.assistantText.slice(0, 200)}`;
567346
+ const loopKey = obs.loopSignal ? `loop:${obs.loopSignal.tool}:${obs.loopSignal.target}:${obs.loopSignal.count}` : "";
567347
+ const sig = `${obs.turn}:${loopKey}:${obs.assistantText.slice(0, 200)}`;
567326
567348
  if (sig === this.lastAuditedSignature)
567327
567349
  return;
567328
567350
  this.pending = obs;
567351
+ this._pendingSig = sig;
567329
567352
  }
567353
+ _pendingSig = "";
567330
567354
  /**
567331
567355
  * Fire the adversary inference if there is a pending observation and no call
567332
567356
  * in flight. Detached/non-blocking — resolves when (or if) a critique lands.
@@ -567337,21 +567361,23 @@ var init_adversaryStream = __esm({
567337
567361
  return null;
567338
567362
  const obs = this.pending;
567339
567363
  this.pending = null;
567340
- this.lastAuditedSignature = `${obs.turn}:${obs.assistantText.slice(0, 200)}`;
567364
+ this.lastAuditedSignature = this._pendingSig || `${obs.turn}:${obs.assistantText.slice(0, 200)}`;
567341
567365
  this.inFlight = true;
567342
567366
  try {
567343
- const resp = await this.backend.chatCompletion({
567344
- messages: [
567345
- { role: "system", content: adversarySystemPrompt() },
567346
- { role: "user", content: buildObservationPrompt(obs, this.ledger) }
567347
- ],
567348
- tools: [],
567349
- temperature: 0,
567350
- maxTokens: 400,
567351
- timeoutMs: this.timeoutMs
567352
- });
567353
- const content = resp.choices?.[0]?.message?.content ?? "";
567354
- const critique2 = parseAdversaryCritique(content);
567367
+ let critique2 = null;
567368
+ for (let attempt = 0; attempt < 2 && !critique2; attempt++) {
567369
+ const resp = await this.backend.chatCompletion({
567370
+ messages: [
567371
+ { role: "system", content: adversarySystemPrompt() },
567372
+ { role: "user", content: buildObservationPrompt(obs, this.ledger) }
567373
+ ],
567374
+ tools: [],
567375
+ temperature: 0,
567376
+ maxTokens: 900,
567377
+ timeoutMs: this.timeoutMs
567378
+ });
567379
+ critique2 = parseAdversaryCritique(resp.choices?.[0]?.message?.content ?? "");
567380
+ }
567355
567381
  if (!critique2)
567356
567382
  return null;
567357
567383
  this.ledger.push({
@@ -567660,6 +567686,164 @@ var init_evidenceBranch = __esm({
567660
567686
  }
567661
567687
  });
567662
567688
 
567689
+ // packages/orchestrator/dist/resolution-memory.js
567690
+ function tokenize6(command) {
567691
+ return command.replace(/[|&;]+/g, " ").split(/\s+/).map((t2) => t2.trim()).filter((t2) => t2.length > 0);
567692
+ }
567693
+ function normalizeErrorKey(error) {
567694
+ const e2 = error.toLowerCase();
567695
+ if (/command not found|not found|no such file|enoent/.test(e2))
567696
+ return "not found";
567697
+ if (/permission denied|eacces/.test(e2))
567698
+ return "permission denied";
567699
+ if (/unrecognized|unknown option|invalid option|no such option/.test(e2))
567700
+ return "bad option";
567701
+ if (/unexpected keyword|got an unexpected/.test(e2))
567702
+ return "bad argument";
567703
+ if (/syntax error|invalid syntax/.test(e2))
567704
+ return "syntax error";
567705
+ if (/module|importerror|modulenotfound/.test(e2))
567706
+ return "missing module";
567707
+ return e2.replace(/['"`].*?['"`]/g, "").replace(/\d+/g, "").replace(/\s+/g, " ").trim().split(" ").slice(0, 6).join(" ");
567708
+ }
567709
+ function isNearVariant(a2, b) {
567710
+ if (a2.length === 0 || b.length === 0)
567711
+ return false;
567712
+ const setA = new Set(a2);
567713
+ const setB = new Set(b);
567714
+ const shared = [...setA].filter((t2) => setB.has(t2));
567715
+ const sharedOperative = shared.some((t2) => t2.length >= 3 && !t2.startsWith("-"));
567716
+ if (!sharedOperative)
567717
+ return false;
567718
+ const onlyA = [...setA].filter((t2) => !setB.has(t2));
567719
+ const onlyB = [...setB].filter((t2) => !setA.has(t2));
567720
+ return onlyA.length > 0 && onlyA.length <= MAX_DELTA_TOKENS && onlyB.length <= MAX_DELTA_TOKENS;
567721
+ }
567722
+ function computeDelta(failed, worked) {
567723
+ const setF = new Set(failed);
567724
+ const setW = new Set(worked);
567725
+ const from3 = failed.filter((t2) => !setW.has(t2));
567726
+ const to = worked.filter((t2) => !setF.has(t2));
567727
+ if (from3.length === 0 || from3.length > MAX_DELTA_TOKENS || to.length > MAX_DELTA_TOKENS) {
567728
+ return null;
567729
+ }
567730
+ return { from: from3, to };
567731
+ }
567732
+ function sameFix(a2, from3, to, errorKey) {
567733
+ return a2.errorKey === errorKey && a2.from.join(" ") === from3.join(" ") && a2.to.join(" ") === to.join(" ");
567734
+ }
567735
+ var WINDOW_TURNS, MAX_DELTA_TOKENS, MAX_FIXES, ResolutionMemory;
567736
+ var init_resolution_memory = __esm({
567737
+ "packages/orchestrator/dist/resolution-memory.js"() {
567738
+ "use strict";
567739
+ WINDOW_TURNS = 6;
567740
+ MAX_DELTA_TOKENS = 3;
567741
+ MAX_FIXES = 200;
567742
+ ResolutionMemory = class {
567743
+ fixes = [];
567744
+ recentFailures = [];
567745
+ /** Load prior fixes (cross-session). */
567746
+ loadFixes(fixes) {
567747
+ for (const f2 of fixes) {
567748
+ if (Array.isArray(f2.from) && Array.isArray(f2.to) && f2.from.length > 0) {
567749
+ this.fixes.push(f2);
567750
+ }
567751
+ }
567752
+ this.fixes.sort((a2, b) => b.count - a2.count);
567753
+ if (this.fixes.length > MAX_FIXES)
567754
+ this.fixes = this.fixes.slice(0, MAX_FIXES);
567755
+ }
567756
+ exportFixes() {
567757
+ return this.fixes;
567758
+ }
567759
+ get size() {
567760
+ return this.fixes.length;
567761
+ }
567762
+ /** Record a failed command (buffered until a resolving success is seen). */
567763
+ recordFailure(input) {
567764
+ if (!input.command)
567765
+ return;
567766
+ this.recentFailures.push({
567767
+ tool: input.tool,
567768
+ command: input.command,
567769
+ tokens: tokenize6(input.command),
567770
+ errorKey: normalizeErrorKey(input.error || ""),
567771
+ turn: input.turn
567772
+ });
567773
+ this.recentFailures = this.recentFailures.filter((f2) => input.turn - f2.turn <= WINDOW_TURNS).slice(-12);
567774
+ }
567775
+ /**
567776
+ * Record a successful command. If it is a near-variant of a recent failure
567777
+ * of the same tool, learn the fix. Returns the learned fix, if any.
567778
+ */
567779
+ recordSuccess(input) {
567780
+ if (!input.command)
567781
+ return null;
567782
+ const workedTokens = tokenize6(input.command);
567783
+ let learned = null;
567784
+ for (let i2 = this.recentFailures.length - 1; i2 >= 0; i2--) {
567785
+ const fail3 = this.recentFailures[i2];
567786
+ if (fail3.tool !== input.tool)
567787
+ continue;
567788
+ if (input.turn - fail3.turn > WINDOW_TURNS)
567789
+ continue;
567790
+ if (fail3.command === input.command)
567791
+ continue;
567792
+ if (!isNearVariant(fail3.tokens, workedTokens))
567793
+ continue;
567794
+ const delta = computeDelta(fail3.tokens, workedTokens);
567795
+ if (!delta)
567796
+ continue;
567797
+ const existing = this.fixes.find((f2) => sameFix(f2, delta.from, delta.to, fail3.errorKey));
567798
+ if (existing) {
567799
+ existing.count++;
567800
+ existing.lastSeen = Date.now();
567801
+ existing.example = { failed: fail3.command, worked: input.command };
567802
+ learned = existing;
567803
+ } else {
567804
+ learned = {
567805
+ from: delta.from,
567806
+ to: delta.to,
567807
+ errorKey: fail3.errorKey,
567808
+ example: { failed: fail3.command, worked: input.command },
567809
+ count: 1,
567810
+ lastSeen: Date.now()
567811
+ };
567812
+ this.fixes.push(learned);
567813
+ }
567814
+ this.recentFailures.splice(i2, 1);
567815
+ break;
567816
+ }
567817
+ if (this.fixes.length > MAX_FIXES) {
567818
+ this.fixes.sort((a2, b) => b.count - a2.count);
567819
+ this.fixes = this.fixes.slice(0, MAX_FIXES);
567820
+ }
567821
+ return learned;
567822
+ }
567823
+ /**
567824
+ * Suggest a learned fix for a command about to run, or null. Matches when the
567825
+ * command contains the fix's `from` token sequence.
567826
+ */
567827
+ suggest(command) {
567828
+ if (!command)
567829
+ return null;
567830
+ const tokens = new Set(tokenize6(command));
567831
+ let best = null;
567832
+ for (const f2 of this.fixes) {
567833
+ if (f2.from.every((t2) => tokens.has(t2))) {
567834
+ if (!best || f2.count > best.count)
567835
+ best = f2;
567836
+ }
567837
+ }
567838
+ if (!best)
567839
+ return null;
567840
+ const message2 = `[LEARNED FIX — from a prior session] A command using \`${best.from.join(" ")}\` failed with "${best.errorKey}"; the working form replaced it with \`${best.to.join(" ")}\` (confirmed ${best.count}× — e.g. \`${best.example.worked}\`). Apply that change here before running, instead of re-discovering it.`;
567841
+ return { fix: best, message: message2 };
567842
+ }
567843
+ };
567844
+ }
567845
+ });
567846
+
567663
567847
  // packages/orchestrator/dist/contextEngine.js
567664
567848
  function estimateTokens3(messages2) {
567665
567849
  const chars = messages2.reduce((sum, message2) => sum + message2.content.length + message2.role.length + (message2.name?.length ?? 0), 0);
@@ -569423,6 +569607,7 @@ var init_agenticRunner = __esm({
569423
569607
  init_evidenceLedger();
569424
569608
  init_adversaryStream();
569425
569609
  init_evidenceBranch();
569610
+ init_resolution_memory();
569426
569611
  init_contextEngine();
569427
569612
  init_prompt_cache();
569428
569613
  TOOL_SUBSETS = {
@@ -569924,6 +570109,12 @@ var init_agenticRunner = __esm({
569924
570109
  // adjacent to the main loop. Initialized per run when the step critic is
569925
570110
  // enabled and a backend is available. See adversaryStream.ts.
569926
570111
  _adversaryStream = null;
570112
+ // Generic, cross-session failure→resolution learning: learns the token-level
570113
+ // change that turned a failed command into a working one (e.g. python→python3)
570114
+ // from observed pairs, and surfaces it before a matching command runs — so
570115
+ // the loop stops re-discovering the same fix every session. See
570116
+ // resolution-memory.ts. Persisted to ~/.omnius/resolution-memory.json.
570117
+ _resolutionMemory = new ResolutionMemory();
569927
570118
  _lastContextFrameDiagnostics = null;
569928
570119
  _lastContextPressureSnapshot = null;
569929
570120
  _lastActiveForgettingReport = null;
@@ -570624,6 +570815,69 @@ Your hypotheses MUST address this specific error, not generic causes.
570624
570815
  }
570625
570816
  return best && best.count >= 3 ? best : null;
570626
570817
  }
570818
+ /**
570819
+ * Backend adapter for AUXILIARY inference (adversary critiques, branch
570820
+ * extraction) — tool-less, think-off, JSON-shaped calls. The main backend's
570821
+ * chatCompletion routes to Ollama's /v1/chat/completions, where qwen3-family
570822
+ * models IGNORE think:false and /no_think and (with no tools to anchor
570823
+ * output) emit a reasoning-only response that gets stripped to EMPTY. The
570824
+ * native /api/chat path honors think:false. This adapter prefers it and sets
570825
+ * a responseFormat so the native path enforces JSON mode. Falls back to
570826
+ * chatCompletion for non-Ollama backends.
570827
+ */
570828
+ _auxInferenceBackend() {
570829
+ const b = this.backend;
570830
+ const useNative = typeof b.nativeOllamaChatCompletion === "function";
570831
+ return {
570832
+ chatCompletion: (req3) => {
570833
+ const r2 = {
570834
+ ...req3,
570835
+ responseFormat: req3.responseFormat ?? { type: "json_object" }
570836
+ };
570837
+ return useNative ? b.nativeOllamaChatCompletion(r2) : b.chatCompletion(r2);
570838
+ }
570839
+ };
570840
+ }
570841
+ /** Cross-session resolution-memory stores: global (machine-wide) + local. */
570842
+ _resolutionMemoryPaths() {
570843
+ const global2 = _pathJoin(_osHomedir(), ".omnius", "resolution-memory.json");
570844
+ const local = _pathJoin(this.omniusStateDir(), "resolution-memory.json");
570845
+ return [global2, local];
570846
+ }
570847
+ _loadResolutionMemory() {
570848
+ const seen = /* @__PURE__ */ new Set();
570849
+ const merged = [];
570850
+ for (const p2 of this._resolutionMemoryPaths()) {
570851
+ try {
570852
+ if (!_fsExistsSync(p2))
570853
+ continue;
570854
+ const data = JSON.parse(_fsReadFileSync(p2, "utf-8"));
570855
+ const arr = Array.isArray(data) ? data : Array.isArray(data?.fixes) ? data.fixes : [];
570856
+ for (const f2 of arr) {
570857
+ const key = `${(f2.from ?? []).join(" ")}=>${(f2.to ?? []).join(" ")}|${f2.errorKey ?? ""}`;
570858
+ if (seen.has(key))
570859
+ continue;
570860
+ seen.add(key);
570861
+ merged.push(f2);
570862
+ }
570863
+ } catch {
570864
+ }
570865
+ }
570866
+ if (merged.length > 0)
570867
+ this._resolutionMemory.loadFixes(merged);
570868
+ }
570869
+ _persistResolutionMemory() {
570870
+ const fixes = this._resolutionMemory.exportFixes();
570871
+ if (fixes.length === 0)
570872
+ return;
570873
+ for (const p2 of this._resolutionMemoryPaths()) {
570874
+ try {
570875
+ _fsMkdirSync(_pathJoin(p2, ".."), { recursive: true });
570876
+ _fsWriteFileSync(p2, JSON.stringify(fixes, null, 2), { mode: 384 });
570877
+ } catch {
570878
+ }
570879
+ }
570880
+ }
570627
570881
  /**
570628
570882
  * Detect a failing approach and return a decisive root-cause directive, or
570629
570883
  * null. Fires when a non-transient error recurs ≥3× in the recent window
@@ -574750,6 +575004,7 @@ Respond with your assessment, then take action.`;
574750
575004
  }
574751
575005
  } catch {
574752
575006
  }
575007
+ this._loadResolutionMemory();
574753
575008
  this._pauseResolve = null;
574754
575009
  this.pendingUserMessages.length = 0;
574755
575010
  this._taskState = {
@@ -575187,7 +575442,9 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575187
575442
  if (this.options.disableAdversaryCritic !== true && this.backend && typeof this.backend.chatCompletion === "function") {
575188
575443
  const persistPath = this._workingDirectory ? _pathJoin(this._workingDirectory, ".omnius", "memory", "adversary-stream.json") : null;
575189
575444
  this._adversaryStream = new AdversaryStream({
575190
- backend: this.backend,
575445
+ // Native /api/chat (think:false honored) — NOT /v1, which returns empty
575446
+ // for tool-less think-off calls on qwen3-family models.
575447
+ backend: this._auxInferenceBackend(),
575191
575448
  persistPath,
575192
575449
  onCritique: (critique2, sourceTurn) => {
575193
575450
  if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
@@ -577323,6 +577580,23 @@ Root cause hypothesis: the argument family may be wrong, a prerequisite may be m
577323
577580
  Corrective action: try a different approach first: read relevant files, adjust arguments, or verify prerequisites.`);
577324
577581
  }
577325
577582
  }
577583
+ if (tc.name === "shell") {
577584
+ const _cmd = typeof tc.arguments?.["command"] === "string" ? tc.arguments["command"] : typeof tc.arguments?.["cmd"] === "string" ? tc.arguments["cmd"] : "";
577585
+ const _sug = _cmd ? this._resolutionMemory.suggest(_cmd) : null;
577586
+ if (_sug) {
577587
+ const _injKey = `resfix:${_sug.fix.from.join(" ")}=>${_sug.fix.to.join(" ")}`;
577588
+ if (!this._errorGuidanceInjected.has(_injKey)) {
577589
+ this._errorGuidanceInjected.add(_injKey);
577590
+ this.pendingUserMessages.push(_sug.message);
577591
+ this.emit({
577592
+ type: "status",
577593
+ content: `Resolution memory: suggested \`${_sug.fix.from.join(" ")}\`→\`${_sug.fix.to.join(" ")}\` before shell call`,
577594
+ turn,
577595
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
577596
+ });
577597
+ }
577598
+ }
577599
+ }
577326
577600
  if (this._errorPatterns.size > 0) {
577327
577601
  for (const [sig, pattern] of this._errorPatterns) {
577328
577602
  if (pattern.tool === tc.name && pattern.count >= 2 && !this._errorGuidanceInjected.has(sig)) {
@@ -577755,6 +578029,24 @@ Use the saved fact to continue the promised synthesis or next concrete step, or
577755
578029
  },
577756
578030
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
577757
578031
  });
578032
+ if (this._adversaryStream && criticDecision.hitNumber >= 2) {
578033
+ const _args = tc.arguments;
578034
+ const _target = String(_args?.["path"] ?? _args?.["file"] ?? _args?.["command"] ?? JSON.stringify(_args ?? {}).slice(0, 80));
578035
+ this._adversaryStream.observe({
578036
+ turn,
578037
+ assistantText: "",
578038
+ recentToolOutcomes: this._adversaryToolOutcomes.slice(-8).map((o2) => ({ tool: o2.tool, succeeded: o2.succeeded, preview: o2.preview })),
578039
+ claimsCompletion: false,
578040
+ loopSignal: {
578041
+ tool: tc.name,
578042
+ target: _target,
578043
+ count: criticDecision.hitNumber,
578044
+ alreadyHave: _existingFp?.result
578045
+ }
578046
+ });
578047
+ void this._adversaryStream.tick().catch(() => {
578048
+ });
578049
+ }
577758
578050
  const _repeatGateMax = this._resolveRepeatGateMax();
577759
578051
  const repeatGateEligible = isReadLike || tc.name === "memory_write";
577760
578052
  if (repeatGateEligible && _existingFp !== void 0 && _repeatGateMax > 0) {
@@ -578411,6 +578703,34 @@ Respond with EXACTLY this structure before your next tool call:
578411
578703
  } catch {
578412
578704
  }
578413
578705
  }
578706
+ {
578707
+ const _cmd = typeof tc.arguments?.["command"] === "string" ? tc.arguments["command"] : typeof tc.arguments?.["cmd"] === "string" ? tc.arguments["cmd"] : "";
578708
+ if (_cmd && tc.name === "shell") {
578709
+ if (!result.success) {
578710
+ this._resolutionMemory.recordFailure({
578711
+ tool: tc.name,
578712
+ command: _cmd,
578713
+ error: result.error ?? result.output ?? "",
578714
+ turn
578715
+ });
578716
+ } else {
578717
+ const _learned = this._resolutionMemory.recordSuccess({
578718
+ tool: tc.name,
578719
+ command: _cmd,
578720
+ turn
578721
+ });
578722
+ if (_learned) {
578723
+ this._persistResolutionMemory();
578724
+ this.emit({
578725
+ type: "status",
578726
+ content: `Learned fix: \`${_learned.from.join(" ")}\` → \`${_learned.to.join(" ")}\` (after "${_learned.errorKey}") — saved for future sessions`,
578727
+ turn,
578728
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578729
+ });
578730
+ }
578731
+ }
578732
+ }
578733
+ }
578414
578734
  if (!result.success && result.error) {
578415
578735
  const errorText = result.error;
578416
578736
  let errorType = "unknown";
@@ -578868,7 +579188,9 @@ Respond with EXACTLY this structure before your next tool call:
578868
579188
  content: fullContent,
578869
579189
  // the REAL body, not the preview
578870
579190
  fileVersion: this._worldFacts.files.get(pRaw)?.writeCount ?? 0,
578871
- backend: this.backend,
579191
+ // Native /api/chat so the extractor LLM fallback isn't
579192
+ // silently empty on qwen3-family models.
579193
+ backend: this._auxInferenceBackend(),
578872
579194
  timeoutMs: 3e4
578873
579195
  });
578874
579196
  output = [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.346",
3
+ "version": "1.0.348",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.346",
9
+ "version": "1.0.348",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.346",
3
+ "version": "1.0.348",
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",