omnius 1.0.345 → 1.0.347

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
@@ -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({
@@ -568855,7 +568881,7 @@ RECOVERY: cd to the directory containing '${file}', run a plain install with no
568855
568881
  });
568856
568882
 
568857
568883
  // packages/orchestrator/dist/agenticRunner.js
568858
- import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync, writeFileSync as _fsWriteFileSync, appendFileSync as _fsAppendFileSync, unlinkSync as _fsUnlinkSync, mkdirSync as _fsMkdirSync } from "node:fs";
568884
+ import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync, writeFileSync as _fsWriteFileSync, appendFileSync as _fsAppendFileSync, unlinkSync as _fsUnlinkSync, mkdirSync as _fsMkdirSync, statSync as _fsStatSync } from "node:fs";
568859
568885
  import { execFile as _execFile, spawn as _spawn } from "node:child_process";
568860
568886
  import { createHash as _createHash } from "node:crypto";
568861
568887
  import { join as _pathJoin, resolve as _pathResolve } from "node:path";
@@ -570624,6 +570650,29 @@ Your hypotheses MUST address this specific error, not generic causes.
570624
570650
  }
570625
570651
  return best && best.count >= 3 ? best : null;
570626
570652
  }
570653
+ /**
570654
+ * Backend adapter for AUXILIARY inference (adversary critiques, branch
570655
+ * extraction) — tool-less, think-off, JSON-shaped calls. The main backend's
570656
+ * chatCompletion routes to Ollama's /v1/chat/completions, where qwen3-family
570657
+ * models IGNORE think:false and /no_think and (with no tools to anchor
570658
+ * output) emit a reasoning-only response that gets stripped to EMPTY. The
570659
+ * native /api/chat path honors think:false. This adapter prefers it and sets
570660
+ * a responseFormat so the native path enforces JSON mode. Falls back to
570661
+ * chatCompletion for non-Ollama backends.
570662
+ */
570663
+ _auxInferenceBackend() {
570664
+ const b = this.backend;
570665
+ const useNative = typeof b.nativeOllamaChatCompletion === "function";
570666
+ return {
570667
+ chatCompletion: (req3) => {
570668
+ const r2 = {
570669
+ ...req3,
570670
+ responseFormat: req3.responseFormat ?? { type: "json_object" }
570671
+ };
570672
+ return useNative ? b.nativeOllamaChatCompletion(r2) : b.chatCompletion(r2);
570673
+ }
570674
+ };
570675
+ }
570627
570676
  /**
570628
570677
  * Detect a failing approach and return a decisive root-cause directive, or
570629
570678
  * null. Fires when a non-transient error recurs ≥3× in the recent window
@@ -575187,7 +575236,9 @@ TASK: ${scrubbedTask}` : scrubbedTask;
575187
575236
  if (this.options.disableAdversaryCritic !== true && this.backend && typeof this.backend.chatCompletion === "function") {
575188
575237
  const persistPath = this._workingDirectory ? _pathJoin(this._workingDirectory, ".omnius", "memory", "adversary-stream.json") : null;
575189
575238
  this._adversaryStream = new AdversaryStream({
575190
- backend: this.backend,
575239
+ // Native /api/chat (think:false honored) — NOT /v1, which returns empty
575240
+ // for tool-less think-off calls on qwen3-family models.
575241
+ backend: this._auxInferenceBackend(),
575191
575242
  persistPath,
575192
575243
  onCritique: (critique2, sourceTurn) => {
575193
575244
  if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
@@ -577755,6 +577806,24 @@ Use the saved fact to continue the promised synthesis or next concrete step, or
577755
577806
  },
577756
577807
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
577757
577808
  });
577809
+ if (this._adversaryStream && criticDecision.hitNumber >= 2) {
577810
+ const _args = tc.arguments;
577811
+ const _target = String(_args?.["path"] ?? _args?.["file"] ?? _args?.["command"] ?? JSON.stringify(_args ?? {}).slice(0, 80));
577812
+ this._adversaryStream.observe({
577813
+ turn,
577814
+ assistantText: "",
577815
+ recentToolOutcomes: this._adversaryToolOutcomes.slice(-8).map((o2) => ({ tool: o2.tool, succeeded: o2.succeeded, preview: o2.preview })),
577816
+ claimsCompletion: false,
577817
+ loopSignal: {
577818
+ tool: tc.name,
577819
+ target: _target,
577820
+ count: criticDecision.hitNumber,
577821
+ alreadyHave: _existingFp?.result
577822
+ }
577823
+ });
577824
+ void this._adversaryStream.tick().catch(() => {
577825
+ });
577826
+ }
577758
577827
  const _repeatGateMax = this._resolveRepeatGateMax();
577759
577828
  const repeatGateEligible = isReadLike || tc.name === "memory_write";
577760
577829
  if (repeatGateEligible && _existingFp !== void 0 && _repeatGateMax > 0) {
@@ -578830,41 +578899,65 @@ Respond with EXACTLY this structure before your next tool call:
578830
578899
  result = await this.offloadEmbeddedImageResult(result, tc.name, turn);
578831
578900
  }
578832
578901
  let output = this.normalizeToolOutput(result, tc.name, tc.arguments, turn);
578833
- if (process.env["OMNIUS_DISABLE_BRANCH_EXTRACT"] !== "1" && this.lookupRegisteredTool(tc.name)?.name === "file_read" && result.success && typeof result.output === "string" && this.backend && typeof this.backend.chatCompletion === "function") {
578902
+ if (process.env["OMNIUS_DISABLE_BRANCH_EXTRACT"] !== "1" && this.lookupRegisteredTool(tc.name)?.name === "file_read" && result.success && this.backend && typeof this.backend.chatCompletion === "function") {
578834
578903
  const a2 = tc.arguments ?? {};
578835
- const hasSmallRange = typeof a2["limit"] === "number" && a2["limit"] <= 80;
578836
- const lineCount = result.output.split("\n").length;
578837
- if (shouldBranchRead(result.output.length, lineCount, hasSmallRange)) {
578838
- const p2 = String(a2["path"] ?? a2["file"] ?? a2["file_path"] ?? "");
578839
- const lastAssistant = [...messages2].reverse().find((m2) => m2.role === "assistant" && typeof m2.content === "string");
578840
- const query = [
578841
- this._taskState.goal ?? "",
578842
- typeof lastAssistant?.content === "string" ? lastAssistant.content : ""
578843
- ].join(" ").trim().slice(0, 400) || "key facts, configuration, and structure";
578844
- try {
578845
- const ev = await extractEvidence({
578846
- path: p2,
578847
- query,
578848
- content: result.output,
578849
- fileVersion: this._worldFacts.files.get(p2)?.writeCount ?? 0,
578850
- backend: this.backend,
578851
- timeoutMs: 3e4
578852
- });
578853
- output = [
578854
- `[BRANCH-EXTRACT] ${p2} is large (${lineCount} lines, ${result.output.length} chars) — read in an isolated branch so it does not flood your context.`,
578855
- `Distilled for: "${query.slice(0, 160)}"`,
578856
- `Relevant evidence (lines ${ev.sourceStart ?? "?"}-${ev.sourceEnd ?? "?"}, confidence ${ev.confidence.toFixed(2)}):`,
578857
- ev.claim,
578858
- `If you need a different region, call file_read with a specific offset+limit, or extract_evidence(path, query) with a sharper question.`
578859
- ].join("\n");
578860
- this.emit({
578861
- type: "status",
578862
- toolName: tc.name,
578863
- content: `Branch-extract: ${p2} (${lineCount} lines) → ${ev.injectedChars} chars to context (${(result.output.length / Math.max(1, ev.injectedChars)).toFixed(0)}× smaller)`,
578864
- turn,
578865
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
578866
- });
578867
- } catch {
578904
+ const hasExplicitRange = typeof a2["offset"] === "number" || typeof a2["limit"] === "number";
578905
+ const pRaw = String(a2["path"] ?? a2["file"] ?? a2["file_path"] ?? "");
578906
+ if (!hasExplicitRange && pRaw) {
578907
+ let fullContent = null;
578908
+ let trueLines = 0;
578909
+ let trueBytes = 0;
578910
+ for (const cand of [
578911
+ pRaw,
578912
+ _pathResolve(this._workingDirectory || process.cwd(), pRaw)
578913
+ ]) {
578914
+ try {
578915
+ const st = _fsStatSync(cand);
578916
+ if (st.isFile()) {
578917
+ trueBytes = st.size;
578918
+ if (trueBytes > 8e3) {
578919
+ fullContent = _fsReadFileSync(cand, "utf-8");
578920
+ trueLines = fullContent.split("\n").length;
578921
+ }
578922
+ break;
578923
+ }
578924
+ } catch {
578925
+ }
578926
+ }
578927
+ if (fullContent && shouldBranchRead(trueBytes, trueLines, false)) {
578928
+ const lastAssistant = [...messages2].reverse().find((m2) => m2.role === "assistant" && typeof m2.content === "string");
578929
+ const query = [
578930
+ this._taskState.goal ?? "",
578931
+ typeof lastAssistant?.content === "string" ? lastAssistant.content : ""
578932
+ ].join(" ").trim().slice(0, 400) || "key facts, configuration, and structure";
578933
+ try {
578934
+ const ev = await extractEvidence({
578935
+ path: pRaw,
578936
+ query,
578937
+ content: fullContent,
578938
+ // the REAL body, not the preview
578939
+ fileVersion: this._worldFacts.files.get(pRaw)?.writeCount ?? 0,
578940
+ // Native /api/chat so the extractor LLM fallback isn't
578941
+ // silently empty on qwen3-family models.
578942
+ backend: this._auxInferenceBackend(),
578943
+ timeoutMs: 3e4
578944
+ });
578945
+ output = [
578946
+ `[BRANCH-EXTRACT] ${pRaw} is large (${trueLines} lines, ${trueBytes} bytes); a whole-file read only returns a preview, so it was read in an isolated branch and distilled.`,
578947
+ `Distilled for: "${query.slice(0, 160)}"`,
578948
+ `Relevant evidence (lines ${ev.sourceStart ?? "?"}-${ev.sourceEnd ?? "?"}, confidence ${ev.confidence.toFixed(2)}):`,
578949
+ ev.claim,
578950
+ `If you need a different region, call file_read with a specific offset+limit. Do NOT re-read the whole file — you already have the relevant content above.`
578951
+ ].join("\n");
578952
+ this.emit({
578953
+ type: "status",
578954
+ toolName: tc.name,
578955
+ content: `Branch-extract: ${pRaw} (${trueLines} lines / ${trueBytes}B) → ${ev.injectedChars} chars to context (${(trueBytes / Math.max(1, ev.injectedChars)).toFixed(0)}× smaller)`,
578956
+ turn,
578957
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578958
+ });
578959
+ } catch {
578960
+ }
578868
578961
  }
578869
578962
  }
578870
578963
  }
@@ -584359,6 +584452,8 @@ ${result}`
584359
584452
  return true;
584360
584453
  if (/fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|UND_ERR|other side closed/i.test(msg))
584361
584454
  return true;
584455
+ if (/stream timeout|no response or chunk within|no response within \d+\s*s|stream stalled/i.test(msg))
584456
+ return true;
584362
584457
  if (/received HTML error page/i.test(msg))
584363
584458
  return true;
584364
584459
  if (/model is loading|server busy|overloaded/i.test(msg))
@@ -584602,7 +584697,7 @@ ${description}`
584602
584697
  if (!this.isTransientError(initialErr))
584603
584698
  return null;
584604
584699
  const errMsg = flattenErrorText(initialErr);
584605
- const isNetworkError2 = /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|socket hang up|UND_ERR|other side closed/i.test(errMsg);
584700
+ const isNetworkError2 = /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|socket hang up|UND_ERR|other side closed|stream timeout|no response or chunk within|no response within \d+\s*s|stream stalled/i.test(errMsg);
584606
584701
  const isAuthError = this.isRecoverableAuthError(initialErr);
584607
584702
  const isGpuSlotUnavailable = this.isGpuSlotUnavailableError(initialErr);
584608
584703
  const maxRetries = isNetworkError2 || isGpuSlotUnavailable || isAuthError ? Infinity : 3;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.345",
3
+ "version": "1.0.347",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.345",
9
+ "version": "1.0.347",
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.345",
3
+ "version": "1.0.347",
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",