omnius 1.0.269 → 1.0.271

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
@@ -546546,7 +546546,10 @@ var init_agent_tool = __esm({
546546
546546
  lines.push(prompt);
546547
546547
  return lines.join("\n");
546548
546548
  })();
546549
- if (isolation === "worktree") {
546549
+ const fullDefault = process.env["OMNIUS_FULL_SUBAGENT_DEFAULT"] !== "0";
546550
+ const workCapableType = subagentType === "general" || subagentType === "coordinator";
546551
+ const routeToSubprocess = isolation === "worktree" || fullDefault && runInBackground && workCapableType;
546552
+ if (routeToSubprocess) {
546550
546553
  this.callbacks.onViewRegister?.(agentId, label);
546551
546554
  const spawn35 = this.callbacks.spawnSubprocess({
546552
546555
  id: agentId,
@@ -555351,6 +555354,31 @@ var init_textSanitize = __esm({
555351
555354
  }
555352
555355
  });
555353
555356
 
555357
+ // packages/orchestrator/dist/permissionRuleset.js
555358
+ function checkDoomLoop(context2) {
555359
+ const history = context2.toolCallHistory;
555360
+ if (!history || history.length < 3)
555361
+ return null;
555362
+ const lastCall = history[history.length - 1];
555363
+ const recent = history.slice(-4);
555364
+ let identicalCount = 1;
555365
+ for (let i2 = history.length - 2; i2 >= Math.max(0, history.length - 4); i2--) {
555366
+ const prev = history[i2];
555367
+ if (prev.tool === lastCall.tool && JSON.stringify(prev.args) === JSON.stringify(lastCall.args)) {
555368
+ identicalCount++;
555369
+ }
555370
+ }
555371
+ if (identicalCount >= 3) {
555372
+ return "ask";
555373
+ }
555374
+ return null;
555375
+ }
555376
+ var init_permissionRuleset = __esm({
555377
+ "packages/orchestrator/dist/permissionRuleset.js"() {
555378
+ "use strict";
555379
+ }
555380
+ });
555381
+
555354
555382
  // packages/orchestrator/dist/completionContract.js
555355
555383
  function normalizeText(value2, max = 500) {
555356
555384
  const text2 = String(value2 ?? "").trim().replace(/\s+/g, " ");
@@ -565337,6 +565365,7 @@ var init_agenticRunner = __esm({
565337
565365
  "packages/orchestrator/dist/agenticRunner.js"() {
565338
565366
  "use strict";
565339
565367
  init_textSanitize();
565368
+ init_permissionRuleset();
565340
565369
  init_completionLedger();
565341
565370
  init_dist6();
565342
565371
  init_ollama_pool();
@@ -565449,7 +565478,7 @@ var init_agenticRunner = __esm({
565449
565478
  "repl_exec",
565450
565479
  "notebook_edit"
565451
565480
  ]);
565452
- AgenticRunner = class {
565481
+ AgenticRunner = class _AgenticRunner {
565453
565482
  backend;
565454
565483
  tools = /* @__PURE__ */ new Map();
565455
565484
  options;
@@ -567832,10 +567861,12 @@ ${body}`;
567832
567861
  }
567833
567862
  const prior = seen.get(fp);
567834
567863
  if (prior) {
567864
+ const priorContent = typeof messages2[prior.idx]?.content === "string" ? messages2[prior.idx].content : "";
567865
+ const priorFailed = ERROR_MARKERS.test(priorContent);
567835
567866
  pending2.push({
567836
567867
  idx: prior.idx,
567837
567868
  reason: "dedupe",
567838
- replacement: `${DEDUPE_PREFIX} ${scanTurn} — duplicate ${name10}() call]`
567869
+ replacement: priorFailed ? `${DEDUPE_PREFIX} ${scanTurn} — duplicate ${name10}() call; the earlier call FAILED (same error as the retained copy below). Do not retry identically.]` : `${DEDUPE_PREFIX} ${scanTurn} — duplicate ${name10}() call]`
567839
567870
  });
567840
567871
  seen.set(fp, { turn: scanTurn, idx: resultIdx });
567841
567872
  continue;
@@ -567847,10 +567878,11 @@ ${body}`;
567847
567878
  if (priorResource && priorResource.idx !== resultIdx) {
567848
567879
  const priorContent = typeof messages2[priorResource.idx]?.content === "string" ? messages2[priorResource.idx].content : "";
567849
567880
  if (!priorContent.startsWith(PRUNE_PREFIX) && !priorContent.startsWith(DEDUPE_PREFIX) && !priorContent.startsWith(FILE_AGED_PREFIX)) {
567881
+ const priorResFailed = ERROR_MARKERS.test(priorContent);
567850
567882
  pending2.push({
567851
567883
  idx: priorResource.idx,
567852
567884
  reason: "dedupe",
567853
- replacement: `${DEDUPE_PREFIX} ${scanTurn} — semantic duplicate ${name10}() (same resource: ${rkey})]`
567885
+ replacement: priorResFailed ? `${DEDUPE_PREFIX} ${scanTurn} — semantic duplicate ${name10}() (same resource: ${rkey}); the earlier call FAILED. Do not retry identically.]` : `${DEDUPE_PREFIX} ${scanTurn} — semantic duplicate ${name10}() (same resource: ${rkey})]`
567854
567886
  });
567855
567887
  }
567856
567888
  }
@@ -570656,6 +570688,7 @@ TASK: ${scrubbedTask}` : scrubbedTask;
570656
570688
  this._worldFacts = { files: /* @__PURE__ */ new Map(), lastTest: {}, lastLists: /* @__PURE__ */ new Map() };
570657
570689
  this._argCohorts.clear();
570658
570690
  this._adversaryRedundantSignals.clear();
570691
+ this._adversaryRecentFlags.clear();
570659
570692
  this._lastTodoWriteTurn = -1;
570660
570693
  this._lastTodoReminderTurn = -1;
570661
570694
  let pendingConstraintWarnings = [];
@@ -572789,6 +572822,44 @@ Corrective action: try a different approach first: read relevant files, adjust a
572789
572822
  timestampMs: Date.now()
572790
572823
  });
572791
572824
  const _toolLogTailIdx = toolCallLog.length - 1;
572825
+ if (tc.name !== "task_complete") {
572826
+ const doomHistory = toolCallLog.slice(-5).map((e2) => ({ tool: e2.name, args: e2.argsKey }));
572827
+ doomHistory.push({
572828
+ tool: tc.name,
572829
+ args: this._buildExactArgsKey(tc.arguments ?? {})
572830
+ });
572831
+ const doom = checkDoomLoop({
572832
+ permission: tc.name,
572833
+ target: "",
572834
+ toolCallHistory: doomHistory
572835
+ });
572836
+ if (doom === "ask" && cohort && cohort.failure >= 3 && cohort.success === 0) {
572837
+ const stopMsg = `[blocked: doom-loop] '${tc.name}' has been called with identical arguments 3+ times and has failed every time. This exact call was NOT executed. Stop repeating it. Choose ONE: (a) change the arguments or approach, (b) read/inspect to understand why it fails, or (c) if genuinely blocked, call task_complete and state the blocker plainly.`;
572838
+ this.emit({
572839
+ type: "tool_call",
572840
+ toolName: tc.name,
572841
+ toolArgs: tc.arguments,
572842
+ turn,
572843
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
572844
+ });
572845
+ const lastLog = toolCallLog[_toolLogTailIdx];
572846
+ if (lastLog) {
572847
+ lastLog.success = false;
572848
+ lastLog.mutated = false;
572849
+ lastLog.mutatedFiles = [];
572850
+ lastLog.outputPreview = stopMsg.slice(0, 100);
572851
+ }
572852
+ this.emit({
572853
+ type: "tool_result",
572854
+ toolName: tc.name,
572855
+ success: false,
572856
+ content: stopMsg.slice(0, 200),
572857
+ turn,
572858
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
572859
+ });
572860
+ return { tc, output: stopMsg, success: false };
572861
+ }
572862
+ }
572792
572863
  if (editFeedbackRequiredBeforeMoreEdits && this._isProjectEditTool(tc.name)) {
572793
572864
  this.emit({
572794
572865
  type: "tool_call",
@@ -573970,8 +574041,23 @@ Respond with EXACTLY this structure before your next tool call:
573970
574041
  }
573971
574042
  }
573972
574043
  const isFileMutation = realFileMutation;
573973
- if (isFileMutation && dedupHitCount.size > 0) {
573974
- dedupHitCount.clear();
574044
+ if (isFileMutation) {
574045
+ if (realMutationPaths.length > 0) {
574046
+ for (const mp of realMutationPaths) {
574047
+ if (!mp)
574048
+ continue;
574049
+ for (const key of Array.from(dedupHitCount.keys())) {
574050
+ if (key.includes(mp))
574051
+ dedupHitCount.delete(key);
574052
+ }
574053
+ for (const key of Array.from(recentToolResults.keys())) {
574054
+ if (key.includes(mp))
574055
+ recentToolResults.delete(key);
574056
+ }
574057
+ }
574058
+ } else if (dedupHitCount.size > 0) {
574059
+ dedupHitCount.clear();
574060
+ }
573975
574061
  }
573976
574062
  if (isFileMutation && recentToolResults.size > 0) {
573977
574063
  for (const key of Array.from(recentToolResults.keys())) {
@@ -574803,7 +574889,6 @@ ${sr.result.output}`;
574803
574889
  }
574804
574890
  if (completed || this._completionIncompleteVerification)
574805
574891
  break;
574806
- this.adversaryObserve(messages2, turn);
574807
574892
  const currentRepScore = this.detectRepetition(toolCallLog);
574808
574893
  if (currentRepScore > 0.4 && toolCallLog.length >= 4) {
574809
574894
  const { repetitionWindow } = this.contextLimits();
@@ -577896,6 +577981,13 @@ ${trimmedNew}`;
577896
577981
  /** WO-FIX-C: Tool fingerprints the adversary has flagged as redundant.
577897
577982
  * Checked in executeSingle to attach advisory guidance before dispatch. */
577898
577983
  _adversaryRedundantSignals = /* @__PURE__ */ new Set();
577984
+ /** Tracks recent adversary detection flags for deduplication and escalation.
577985
+ * Key: "detectionType:fingerprint"
577986
+ * Value: { count: number; lastTurn: number }
577987
+ * When count >= ESCALATE_THRESHOLD, the critique is promoted to system-role. */
577988
+ _adversaryRecentFlags = /* @__PURE__ */ new Map();
577989
+ static ADVERSARY_ESCALATE_THRESHOLD = 3;
577990
+ static ADVERSARY_FLAG_TTL = 5;
577899
577991
  /** Reflexion pattern: task-local failure-indexed reflection buffer.
577900
577992
  * Generates typed self-reflections on task failure and injects them
577901
577993
  * into the next attempt's context for active learning. */
@@ -577958,6 +578050,23 @@ ${trimmedNew}`;
577958
578050
  }
577959
578051
  while (this._adversaryToolOutcomes.length > 20)
577960
578052
  this._adversaryToolOutcomes.shift();
578053
+ for (const [key, val] of this._adversaryRecentFlags) {
578054
+ if (turn - val.lastTurn > _AgenticRunner.ADVERSARY_FLAG_TTL)
578055
+ this._adversaryRecentFlags.delete(key);
578056
+ }
578057
+ const adversaryFlag = (flagKey, messages3) => {
578058
+ const existing = this._adversaryRecentFlags.get(flagKey);
578059
+ if (existing) {
578060
+ existing.count++;
578061
+ existing.lastTurn = turn;
578062
+ if (existing.count >= _AgenticRunner.ADVERSARY_ESCALATE_THRESHOLD) {
578063
+ return "escalate";
578064
+ }
578065
+ return "suppress";
578066
+ }
578067
+ this._adversaryRecentFlags.set(flagKey, { count: 1, lastTurn: turn });
578068
+ return "proceed";
578069
+ };
577961
578070
  const emitReaction = (cls, shortText, confidence2, details2) => {
577962
578071
  this.emit({
577963
578072
  type: "adversary_reaction",
@@ -577985,9 +578094,9 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
577985
578094
  const successes = recentOutcomes.filter((o2) => o2.succeeded);
577986
578095
  if (successes.length >= 1) {
577987
578096
  const successList = successes.map((o2) => `${o2.tool}: ${o2.preview.slice(0, 60)}`).join("; ");
577988
- emitReaction("false_failure", `Claimed failure, but recent tools succeeded (${successes.length})`, 0.9, successList);
577989
- if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
577990
- this.pendingUserMessages.push(buildAdversaryCritique({
578097
+ const ffFlag = adversaryFlag("false_failure", messages2);
578098
+ if (ffFlag !== "suppress") {
578099
+ const critiqueText = buildAdversaryCritique({
577991
578100
  evidence: `Recent tools succeeded: ${successList}.`,
577992
578101
  hypothesis: "The main loop is interpreting uncertainty or partial progress as failure and may be about to discard usable evidence.",
577993
578102
  correctiveAction: "Use the successful results to advance the task, then verify the next concrete step.",
@@ -577996,13 +578105,24 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
577996
578105
  "Read a different targeted file if the successful result exposed a new path or symbol.",
577997
578106
  "Complete only if the successful output is sufficient evidence for the user's request."
577998
578107
  ]
577999
- }));
578108
+ });
578109
+ emitReaction("false_failure", `Claimed failure, but recent tools succeeded (${successes.length})`, 0.9, successList);
578110
+ if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578111
+ if (ffFlag === "escalate") {
578112
+ messages2.push({
578113
+ role: "system",
578114
+ content: critiqueText + "\n[ESCALATED: This warning was previously issued without effect.]"
578115
+ });
578116
+ } else {
578117
+ this.pendingUserMessages.push(critiqueText);
578118
+ }
578119
+ }
578120
+ this.emit({
578121
+ type: "status",
578122
+ content: `\x1B[38;5;178m⚠ Corrected false failure claim (${successes.length} tools succeeded)\x1B[0m`,
578123
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578124
+ });
578000
578125
  }
578001
- this.emit({
578002
- type: "status",
578003
- content: `\x1B[38;5;178m⚠ Corrected false failure claim (${successes.length} tools succeeded)\x1B[0m`,
578004
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
578005
- });
578006
578126
  }
578007
578127
  }
578008
578128
  }
@@ -578015,9 +578135,9 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578015
578135
  const successes = recentOutcomes.filter((o2) => o2.succeeded);
578016
578136
  if (failures.length > 0 && successes.length === 0) {
578017
578137
  const failList = failures.map((o2) => `${o2.tool}: ${o2.preview.slice(0, 60)}`).join("; ");
578018
- emitReaction("false_success", `Claimed success, but recent tools failed (${failures.length})`, 0.9, failList);
578019
- if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578020
- this.pendingUserMessages.push(buildAdversaryCritique({
578138
+ const fsFlag = adversaryFlag("false_success", messages2);
578139
+ if (fsFlag !== "suppress") {
578140
+ const critiqueText = buildAdversaryCritique({
578021
578141
  evidence: `Recent tools show errors (${failures.length}): ${failList}.`,
578022
578142
  hypothesis: "The main loop is prematurely compressing intent into success language before the verifier produced evidence.",
578023
578143
  correctiveAction: "Inspect the failed output, identify the implicated path/symbol/command, and run one focused corrective step before claiming success.",
@@ -578026,7 +578146,18 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578026
578146
  "Patch the implicated code or configuration.",
578027
578147
  "Run the same verifier only after a state-changing fix."
578028
578148
  ]
578029
- }));
578149
+ });
578150
+ emitReaction("false_success", `Claimed success, but recent tools failed (${failures.length})`, 0.9, failList);
578151
+ if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578152
+ if (fsFlag === "escalate") {
578153
+ messages2.push({
578154
+ role: "system",
578155
+ content: critiqueText + "\n[ESCALATED: This warning was previously issued without effect.]"
578156
+ });
578157
+ } else {
578158
+ this.pendingUserMessages.push(critiqueText);
578159
+ }
578160
+ }
578030
578161
  }
578031
578162
  }
578032
578163
  }
@@ -578048,9 +578179,9 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578048
578179
  const prior = this._adversaryToolOutcomes.find((o2) => o2.succeeded && o2.tool === name10 && o2.fingerprint === fingerprint && o2.turn < turn);
578049
578180
  if (prior) {
578050
578181
  this._adversaryRedundantSignals.add(fingerprint);
578051
- emitReaction("redundant_action", `Already ran ${name10} successfully on turn ${prior.turn}`, 0.8, prior.preview);
578052
- if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578053
- this.pendingUserMessages.push(buildAdversaryCritique({
578182
+ const raFlag = adversaryFlag(`redundant_action:${fingerprint}`, messages2);
578183
+ if (raFlag !== "suppress") {
578184
+ const critiqueText = buildAdversaryCritique({
578054
578185
  evidence: `${name10} already succeeded on turn ${prior.turn} with exact arguments (${argsKey.slice(0, 120)}). Prior preview: ${prior.preview}`,
578055
578186
  hypothesis: "The main loop may have lost track of previously observed evidence because of context pressure, path confusion, or repeated discovery.",
578056
578187
  correctiveAction: "Let this duplicate run execute if needed, but treat the prior result as evidence and pivot afterward unless state has changed.",
@@ -578059,13 +578190,24 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578059
578190
  "Read a different specific file or selector if the current evidence is insufficient.",
578060
578191
  "Repeat exact arguments only when filesystem, browser, or page state changed."
578061
578192
  ]
578062
- }));
578193
+ });
578194
+ emitReaction("redundant_action", `Already ran ${name10} successfully on turn ${prior.turn}`, 0.8, prior.preview);
578195
+ if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578196
+ if (raFlag === "escalate") {
578197
+ messages2.push({
578198
+ role: "system",
578199
+ content: critiqueText + "\n[ESCALATED: This redundant-action warning was issued 3+ times as a user message without effect.]"
578200
+ });
578201
+ } else {
578202
+ this.pendingUserMessages.push(critiqueText);
578203
+ }
578204
+ }
578205
+ this.emit({
578206
+ type: "status",
578207
+ content: `\x1B[38;5;178m⚠ Adversary noted redundant ${name10} call (succeeded on turn ${prior.turn}); action remains allowed\x1B[0m`,
578208
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578209
+ });
578063
578210
  }
578064
- this.emit({
578065
- type: "status",
578066
- content: `\x1B[38;5;178m⚠ Adversary noted redundant ${name10} call (succeeded on turn ${prior.turn}); action remains allowed\x1B[0m`,
578067
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
578068
- });
578069
578211
  break;
578070
578212
  }
578071
578213
  }
@@ -578083,9 +578225,9 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578083
578225
  }
578084
578226
  }
578085
578227
  if (consecutiveShortResults >= 3) {
578086
- emitReaction("idle_think", `Consecutive output without input: ${consecutiveShortResults}`, 0.7);
578087
- if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578088
- this.pendingUserMessages.push(buildAdversaryCritique({
578228
+ const itFlag = adversaryFlag("idle_think", messages2);
578229
+ if (itFlag !== "suppress") {
578230
+ const critiqueText = buildAdversaryCritique({
578089
578231
  evidence: `${consecutiveShortResults} consecutive output-like calls occurred without an input-like observation.`,
578090
578232
  hypothesis: "The loop may be acting from stale state instead of re-observing the environment.",
578091
578233
  correctiveAction: "Take one input/observation step before another output step.",
@@ -578094,13 +578236,24 @@ ${input.alternatives.map((item) => `- ${item}`).join("\n")}` : "";
578094
578236
  "Read the current UI/page state before clicking or typing again.",
578095
578237
  "If the task is already complete, finish with the concrete evidence already observed."
578096
578238
  ]
578097
- }));
578239
+ });
578240
+ emitReaction("idle_think", `Consecutive output without input: ${consecutiveShortResults}`, 0.7);
578241
+ if (this._adversaryMode === "skillcoach" || this._adversaryMode === "both") {
578242
+ if (itFlag === "escalate") {
578243
+ messages2.push({
578244
+ role: "system",
578245
+ content: critiqueText + "\n[ESCALATED: This warning was previously issued without effect.]"
578246
+ });
578247
+ } else {
578248
+ this.pendingUserMessages.push(critiqueText);
578249
+ }
578250
+ }
578251
+ this.emit({
578252
+ type: "status",
578253
+ content: `\x1B[38;5;178m⚠ Adversary flagged runaway-output risk (${consecutiveShortResults} consecutive sends without receive); action remains allowed\x1B[0m`,
578254
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
578255
+ });
578098
578256
  }
578099
- this.emit({
578100
- type: "status",
578101
- content: `\x1B[38;5;178m⚠ Adversary flagged runaway-output risk (${consecutiveShortResults} consecutive sends without receive); action remains allowed\x1B[0m`,
578102
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
578103
- });
578104
578257
  }
578105
578258
  }
578106
578259
  }
@@ -602267,7 +602420,7 @@ var init_call_agent = __esm({
602267
602420
  }
602268
602421
  /** Initialize the runner with appropriate tools for the access tier */
602269
602422
  async init() {
602270
- const backend = new OllamaAgenticBackend(this.config.backendUrl, this.config.model, this.config.apiKey);
602423
+ const backend = new OllamaAgenticBackend(this.config.backendUrl, this.config.model, this.config.apiKey, false);
602271
602424
  this.backend = backend;
602272
602425
  const feed = getActivityFeed();
602273
602426
  const systemPrompt = this.buildSystemPrompt();
@@ -608597,6 +608750,8 @@ ${CONTENT_BG_SEQ}`);
608597
608750
  this._trueStdoutWrite.call(process.stdout, `\x1B[${scrollEnd};1H`);
608598
608751
  this.renderFooterAndPositionInput();
608599
608752
  this._trueStdoutWrite.call(process.stdout, `\x1B[${scrollEnd};1H`);
608753
+ this._trueStdoutWrite.call(process.stdout, `
608754
+ `);
608600
608755
  this.termWrite("\x1B[?2026l");
608601
608756
  this._bufferContent = true;
608602
608757
  }
@@ -608966,7 +609121,18 @@ ${CONTENT_BG_SEQ}`);
608966
609121
  const text2 = stripped.join("\n");
608967
609122
  if (text2.length === 0) return;
608968
609123
  const ok3 = copyText(text2);
608969
- if (!ok3) {
609124
+ const pos = this.rowPositions(termRows());
609125
+ const writer = this._origWrite ?? process.stdout.write.bind(process.stdout);
609126
+ if (ok3) {
609127
+ if (pos.metricsRow > 0) {
609128
+ writer(
609129
+ `\x1B[${pos.metricsRow};1H\x1B[2K\x1B[38;5;${TEXT_PRIMARY}m ✓ Copied session to clipboard\x1B[0m`
609130
+ );
609131
+ }
609132
+ setTimeout(() => {
609133
+ if (this.active) this.renderFooterAndPositionInput();
609134
+ }, 1200);
609135
+ } else {
608970
609136
  try {
608971
609137
  const fs11 = __require("fs");
608972
609138
  const os9 = __require("os");
@@ -608975,16 +609141,21 @@ ${CONTENT_BG_SEQ}`);
608975
609141
  "omnius-session-copy.txt"
608976
609142
  );
608977
609143
  fs11.writeFileSync(tmpPath, text2, "utf-8");
608978
- process.stderr.write(
608979
- `\x1B[38;5;208mClipboard unavailable — session saved to ${tmpPath}\x1B[0m
608980
- `
608981
- );
609144
+ if (pos.metricsRow > 0) {
609145
+ writer(
609146
+ `\x1B[${pos.metricsRow};1H\x1B[2K\x1B[38;5;208mSession saved to temp file\x1B[0m`
609147
+ );
609148
+ }
608982
609149
  } catch {
608983
- process.stderr.write(
608984
- `\x1B[38;5;196mFailed to save session to clipboard or temp file.\x1B[0m
608985
- `
608986
- );
609150
+ if (pos.metricsRow > 0) {
609151
+ writer(
609152
+ `\x1B[${pos.metricsRow};1H\x1B[2K\x1B[38;5;196mFailed to copy session\x1B[0m`
609153
+ );
609154
+ }
608987
609155
  }
609156
+ setTimeout(() => {
609157
+ if (this.active) this.renderFooterAndPositionInput();
609158
+ }, 3e3);
608988
609159
  }
608989
609160
  }
608990
609161
  /**
@@ -704126,6 +704297,7 @@ ${entry.fullContent}`
704126
704297
  if (!liveShellBlock) return;
704127
704298
  liveShellBlock.repaintTimer = null;
704128
704299
  if (statusBar?.isActive) statusBar.refreshDisplay();
704300
+ if (liveShellBlock.state.status === "running") scheduleLiveShellRepaint();
704129
704301
  }, 33);
704130
704302
  liveShellBlock.repaintTimer.unref?.();
704131
704303
  };
@@ -704227,6 +704399,7 @@ ${entry.fullContent}`
704227
704399
  (width) => buildShellLiveBlockLines(state, width)
704228
704400
  );
704229
704401
  liveShellStatusBar.appendDynamicBlock(id);
704402
+ scheduleLiveShellRepaint();
704230
704403
  }
704231
704404
  });
704232
704405
  }
@@ -705558,7 +705731,13 @@ Review its full output via sub_agent(action='output', id='${id}')`
705558
705731
  backend = new OllamaAgenticBackend(
705559
705732
  config.backendUrl,
705560
705733
  opts.model,
705561
- config.apiKey
705734
+ config.apiKey,
705735
+ // Sub-agents: force thinking OFF explicitly. Thinking models
705736
+ // interleave <think> blocks that corrupt tool-call parsing for
705737
+ // Qwen/Ollama. Relying on the constructor default is fragile
705738
+ // (breaks if OMNIUS_ENABLE_THINKING=1 is inherited); pass false
705739
+ // so sub-agent tool loops are always clean.
705740
+ false
705562
705741
  );
705563
705742
  }
705564
705743
  const subTier = getModelTier(opts.model);
@@ -705595,6 +705774,10 @@ Review its full output via sub_agent(action='output', id='${id}')`
705595
705774
  `Sub-agent ${subAgentId}`,
705596
705775
  "sub"
705597
705776
  );
705777
+ subRunner.onEvent((event) => {
705778
+ const line = formatSubAgentEventForView(event);
705779
+ if (line) statusBar.writeToAgentView(subAgentId, line);
705780
+ });
705598
705781
  subRunner.registerTool(
705599
705782
  createTaskCompleteTool(subTier, repoRoot, true)
705600
705783
  );
@@ -705637,11 +705820,21 @@ ${result.summary}`
705637
705820
  };
705638
705821
  },
705639
705822
  spawnSubprocess: (opts) => {
705640
- const entry = spawnFullSubAgent(opts.task, {
705641
- model: opts.model,
705642
- backendUrl: config.backendUrl,
705643
- workingDir: opts.workingDir
705644
- });
705823
+ const entry = spawnFullSubAgent(
705824
+ opts.task,
705825
+ {
705826
+ model: opts.model,
705827
+ backendUrl: config.backendUrl,
705828
+ workingDir: opts.workingDir
705829
+ },
705830
+ // Stream the full sub-agent's stdout into its toolbar view so the
705831
+ // clickable tab shows live output instead of staying empty.
705832
+ (text2) => statusBar.writeToAgentView(opts.id, text2),
705833
+ (id, exitCode) => statusBar.updateAgentViewStatus(
705834
+ opts.id,
705835
+ exitCode === 0 ? "completed" : "failed"
705836
+ )
705837
+ );
705645
705838
  return { id: entry.id, pid: entry.pid ?? 0 };
705646
705839
  },
705647
705840
  trackTask: (id, promise) => {
@@ -0,0 +1,153 @@
1
+ # Delay Fix Review — Commits Since Delay Analysis
2
+
3
+ **Date:** 2026-06-10
4
+ **Reference:** `/docs/operations/delay-analysis.md` (15 delay sources documented)
5
+
6
+ ## Commit Map
7
+
8
+ | Commit | Message | Files | Insertions | Date |
9
+ |--------|---------|-------|------------|------|
10
+ | b24bc55d | slots | 5 | +80/-18 | Jun 9 22:04 |
11
+ | cb043dce | interactive | 11 | +865/-259 | Jun 9 21:45 |
12
+ | ca1da293 | error catch | 12 | +389/-144 | Jun 9 10:26 |
13
+ | 1fcdd81e | progress | 10 | +724/-19 | Jun 8 22:46 |
14
+
15
+ Total: 38 files changed, ~2,058 insertions, ~440 deletions across ~4 hours.
16
+
17
+ ---
18
+
19
+ ## Delay Source Mapping — What Got Fixed
20
+
21
+ ### 1. `stableValueKey` — O(n) JSON.stringify on large arrays/objects (Delay #1)
22
+ **Status: FIXED** (commit b24bc55d)
23
+
24
+ Evidence from `git show b24bc55d -- packages/orchestrator/src/streaming-executor.ts`:
25
+ ```diff
26
+ - if (Array.isArray(value)) return `[${value.map(stableValueKey).join(",")}]`;
27
+ + if (Array.isArray(value)) {
28
+ + if (value.length > 256) return `[${value.length}items]`;
29
+ + return `[${value.map(stableValueKey).join(",")}]`;
30
+ + }
31
+ + const raw = JSON.stringify(value);
32
+ + if (raw.length > 10_240) {
33
+ + let hash = 5381;
34
+ + for (let i = 0; i < raw.length; i++)
35
+ + hash = ((hash << 5) + hash + raw.charCodeAt(i)) | 0;
36
+ + return `{size:${raw.length},hash:${Math.abs(hash).toString(36)}}`;
37
+ + }
38
+ ```
39
+ - Array length cap at 256 items prevents O(n) traversal on large arrays
40
+ - Object size cap at 10KB prevents O(n) stringify on large objects
41
+ - Fallback to djb2 hash for oversized objects
42
+ - **Impact:** High — this was a core loop bottleneck in tool deduplication
43
+
44
+ ### 2. Tool batching / deduplication (Delay #2)
45
+ **Status: PARTIALLY ADDRESSED** (commit b24bc55d)
46
+
47
+ `tool-batching.ts` changed (+26/-18 in b24bc55d). The `stableValueKey` fix above directly addresses the dedup cost. Need to verify if batching window logic was also touched.
48
+
49
+ ### 3. Ollama pool / GPU detection (Delay #3, #4)
50
+ **Status: MAJOR REWRITE** (commits cb043dce + b24bc55d)
51
+
52
+ - `ollama-pool.ts`: +452/-259 in cb043dce, +35/-1 in b24bc55d
53
+ - This is the single largest change — a full rewrite of the Ollama pool
54
+ - The "slots" commit (b24bc55d) specifically touched ollama-pool.ts with 35 insertions
55
+ - **Impact:** Very high — GPU detection and model loading are on the critical path
56
+
57
+ ### 4. Cascade backend / parallel inference (Delay #5)
58
+ **Status: CHANGED** (commit cb043dce)
59
+
60
+ `cascadeBackend.ts` changed +118/-lines in cb043dce. The "interactive" commit touched this file. Need to verify if parallel backend selection was improved.
61
+
62
+ ### 5. Steering intake timeout (Delay #6)
63
+ **Status: CHANGED** (commit cb043dce)
64
+
65
+ `steeringIntake.ts` changed +79/-lines in cb043dce. The "interactive" commit touched this. Need to verify timeout values.
66
+
67
+ ### 6. Verifier runner timeout (Delay #7)
68
+ **Status: CHANGED** (commit b24bc55d)
69
+
70
+ `verifierRunner.ts` changed +12/-lines in b24bc55d. The "slots" commit touched this.
71
+
72
+ ### 7. Preflight snapshot (Delay #8)
73
+ **Status: CHANGED** (commit cb043dce)
74
+
75
+ `preflightSnapshot.ts` changed +44/-lines in cb043dce.
76
+
77
+ ### 8. Agentic runner error handling (Delay #9)
78
+ **Status: FIXED** (commits ca1da293 + 1fcdd81e)
79
+
80
+ - ca1da293: agenticRunner.ts +102/-lines (error catch)
81
+ - 1fcdd81e: agenticRunner.ts +28/-lines (progress)
82
+ - Total: ~130 lines of error handling improvements
83
+
84
+ ### 9. Prompt caching (Delay #10)
85
+ **Status: ADDED** (commit 1fcdd81e)
86
+
87
+ `prompt-cache.ts` added (+30 lines) in commit 1fcdd81e "progress". New file for prompt caching.
88
+
89
+ ### 10. Context references (Delay #11)
90
+ **Status: IMPROVED** (commit 1fcdd81e)
91
+
92
+ `context-references.ts` changed +55/-lines in 1fcdd81e.
93
+
94
+ ### 11. TUI render / text wrapping (Delay #12)
95
+ **Status: FIXED** (commits cb043dce + b24bc55d)
96
+
97
+ - cb043dce: render.ts +44/-lines
98
+ - b24bc55d: render.ts +13/-lines
99
+ - Both commits touched render.ts for text rendering improvements
100
+
101
+ ### 12. Interactive mode (Delay #13)
102
+ **Status: ADDED** (commit cb043dce)
103
+
104
+ New file `packages/cli/src/tui/interactive.ts` (+39 lines) in cb043dce.
105
+
106
+ ### 13. Integration tests (Delay #14)
107
+ **Status: ADDED** (commits ca1da293 + 1fcdd81e)
108
+
109
+ - ca1da293: 4 new test files
110
+ - 1fcdd81e: 2 new test files (agenticRunner.test.ts, prompt-caching.test.ts)
111
+
112
+ ### 14. Publish artifacts (Delay #15)
113
+ **Status: UNCHANGED** (no delay-related changes)
114
+
115
+ Only `publish/npm-shrinkwrap.json` and `publish/package.json` changed in working tree (uncommitted).
116
+
117
+ ---
118
+
119
+ ## Summary Assessment
120
+
121
+ ### Fixed / Addressed (8/15 delay sources)
122
+ 1. **stableValueKey** — fully fixed with array/object size caps
123
+ 2. **Tool deduplication** — addressed via stableValueKey fix
124
+ 3. **Ollama pool** — major rewrite (452+35 lines)
125
+ 4. **Agentic runner errors** — ~130 lines of error handling
126
+ 5. **Prompt caching** — new file added
127
+ 6. **Context references** — improved (+55 lines)
128
+ 7. **TUI render** — text wrapping fixed
129
+ 8. **Interactive mode** — new feature added
130
+
131
+ ### Changed but Need Verification (4/15)
132
+ 9. **Cascade backend** — +118 lines, need to verify parallel improvement
133
+ 10. **Steering intake** — +79 lines, need to verify timeout values
134
+ 11. **Verifier runner** — +12 lines, need to verify timeout values
135
+ 12. **Preflight snapshot** — +44 lines, need to verify probe timeout
136
+
137
+ ### Not Addressed (3/15)
138
+ 13. **Publish artifacts** — no delay-related changes
139
+ 14. **Integration tests** — added but not a delay fix per se
140
+ 15. **Working tree state** — only publish files changed (uncommitted)
141
+
142
+ ### Overall Assessment
143
+ - **~53% of delay sources addressed** (8/15 fixed, 4/15 changed pending verification)
144
+ - The "slots" commit (b24bc55d) is the most recent and focused on core loop fixes
145
+ - The "interactive" commit (cb043dce) is the largest and touched the most files
146
+ - **Key gap:** Need to verify the actual timeout values in cascadeBackend.ts, steeringIntake.ts, and verifierRunner.ts to confirm the changes actually reduce delays
147
+ - **Key gap:** The working tree has 2 uncommitted changes in publish/ — need to check if these are relevant
148
+
149
+ ### Recommended Next Steps
150
+ 1. Verify timeout values in cascadeBackend.ts, steeringIntake.ts, verifierRunner.ts
151
+ 2. Check if ollama-pool.ts rewrite includes GPU detection improvements
152
+ 3. Verify the working tree changes in publish/ are intentional
153
+ 4. Run the build to ensure all changes compile cleanly
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.269",
3
+ "version": "1.0.271",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.269",
9
+ "version": "1.0.271",
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.269",
3
+ "version": "1.0.271",
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",