omnius 1.0.444 → 1.0.446

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
@@ -298591,13 +298591,29 @@ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark comp
298591
298591
  durationMs: performance.now() - start2
298592
298592
  };
298593
298593
  }
298594
+ const inProgressIndexes = incoming.map((todo, index) => todo.status === "in_progress" ? index : -1).filter((index) => index >= 0);
298595
+ if (inProgressIndexes.length === 0) {
298596
+ const firstPending = incoming.findIndex((todo) => todo.status === "pending");
298597
+ if (firstPending >= 0) {
298598
+ incoming[firstPending] = { ...incoming[firstPending], status: "in_progress" };
298599
+ repairNotes.push("promoted first pending todo to in_progress because a non-empty active checklist must name the current task");
298600
+ }
298601
+ } else if (inProgressIndexes.length > 1) {
298602
+ const keep = inProgressIndexes[0];
298603
+ for (const index of inProgressIndexes.slice(1)) {
298604
+ incoming[index] = { ...incoming[index], status: "pending" };
298605
+ }
298606
+ repairNotes.push(`kept todo ${keep + 1} in_progress and demoted extra in_progress todos to pending`);
298607
+ }
298594
298608
  const sessionId = typeof args["session_id"] === "string" && args["session_id"].trim() ? args["session_id"].trim() : typeof args["sessionId"] === "string" && args["sessionId"].trim() ? args["sessionId"].trim() : getTodoSessionId();
298595
298609
  const oldTodos = readTodos(sessionId);
298596
298610
  const canonicalize2 = (todos) => JSON.stringify(todos.map((t2) => ({
298597
298611
  content: t2.content,
298598
298612
  status: t2.status,
298599
298613
  parentId: t2.parentId ?? null,
298600
- blocker: t2.blocker ?? null
298614
+ blocker: t2.blocker ?? null,
298615
+ verifyCommand: t2.verifyCommand ?? null,
298616
+ declaredArtifacts: Array.isArray(t2.declaredArtifacts) ? [...t2.declaredArtifacts].sort() : null
298601
298617
  })));
298602
298618
  const oldKey = canonicalize2(oldTodos);
298603
298619
  const newKey = canonicalize2(incoming);
@@ -298607,8 +298623,10 @@ Mark tasks complete IMMEDIATELY after finishing — don't batch. Never mark comp
298607
298623
  output: JSON.stringify({
298608
298624
  reminder: "[NO-OP] You called todo_write with the same plan you already have. The list is unchanged. Use todo_write ONLY to add new tasks, mark a task in_progress / completed, or record a blocker — not as a way to re-read your plan (use todo_read for that, but the current plan is also surfaced automatically in your context). Proceed with the current in_progress task.",
298609
298625
  todos: oldTodos,
298610
- noop: true
298626
+ noop: true,
298627
+ noProgress: true
298611
298628
  }),
298629
+ noop: true,
298612
298630
  durationMs: performance.now() - start2
298613
298631
  };
298614
298632
  }
@@ -572729,6 +572747,7 @@ var init_context_fabric = __esm({
572729
572747
  "use strict";
572730
572748
  PRIORITY = {
572731
572749
  GOAL: 100,
572750
+ ACTION_CONTRACT: 98,
572732
572751
  USER_STEERING: 95,
572733
572752
  TASK_STATE: 80,
572734
572753
  KNOWN_FILES: 70,
@@ -572746,6 +572765,7 @@ var init_context_fabric = __esm({
572746
572765
  KIND_ORDER = [
572747
572766
  "goal",
572748
572767
  "user_steering",
572768
+ "action_contract",
572749
572769
  "task_state",
572750
572770
  "known_files",
572751
572771
  "recent_failure",
@@ -572762,6 +572782,7 @@ var init_context_fabric = __esm({
572762
572782
  KIND_LABELS = {
572763
572783
  goal: "Goal",
572764
572784
  user_steering: "User Steering",
572785
+ action_contract: "Next Action Contract",
572765
572786
  task_state: "Task State",
572766
572787
  known_files: "Known Files",
572767
572788
  recent_failure: "Recent Failures",
@@ -572778,6 +572799,7 @@ var init_context_fabric = __esm({
572778
572799
  DEFAULT_KIND_CHAR_BUDGET = {
572779
572800
  goal: 900,
572780
572801
  user_steering: 1400,
572802
+ action_contract: 1800,
572781
572803
  task_state: 1800,
572782
572804
  known_files: 2400,
572783
572805
  recent_failure: 2600,
@@ -574715,6 +574737,11 @@ var init_focusSupervisor = __esm({
574715
574737
  return;
574716
574738
  }
574717
574739
  if (input.toolName === "todo_write" && input.success) {
574740
+ if (input.noop) {
574741
+ this.lastDecision = "todo_noop_not_satisfied";
574742
+ this.lastReason = "todo_write returned no-op; checklist state did not change";
574743
+ return;
574744
+ }
574718
574745
  this.clearSatisfiedDirective("todo plan updated", input.turn);
574719
574746
  return;
574720
574747
  }
@@ -578304,23 +578331,248 @@ ${parts.join("\n")}
578304
578331
  }
578305
578332
  return null;
578306
578333
  }
578334
+ _currentWorkboardSnapshotForFrame() {
578335
+ const dir = this._workboardDir();
578336
+ const fromDisk = readActiveWorkboardSnapshot(dir, this._sessionId);
578337
+ if (fromDisk) {
578338
+ this._workboard = fromDisk;
578339
+ return fromDisk;
578340
+ }
578341
+ if (this._workboard)
578342
+ return this._workboard;
578343
+ const goal = this._taskState.originalGoal || this._taskState.goal || "";
578344
+ if (this._initialWorkboardCardsForGoal(goal).length === 0)
578345
+ return null;
578346
+ return this.getOrCreateWorkboard();
578347
+ }
578348
+ _workboardEvidenceCount(card) {
578349
+ return card?.evidence?.length ?? 0;
578350
+ }
578351
+ _workboardHasEditEvidence(card) {
578352
+ if (!card)
578353
+ return false;
578354
+ const editTools = /* @__PURE__ */ new Set(["file_write", "file_edit", "batch_edit", "file_patch"]);
578355
+ return card.evidence.some((e2) => {
578356
+ if (typeof e2.tool === "string" && editTools.has(e2.tool))
578357
+ return true;
578358
+ return e2.tool === "shell" && /filesystem mutation/i.test(e2.summary ?? "");
578359
+ });
578360
+ }
578361
+ _workboardHasVerificationEvidence(card) {
578362
+ if (!card)
578363
+ return false;
578364
+ return card.evidence.some((e2) => e2.tool === "shell" || e2.tool === "background_run");
578365
+ }
578366
+ _deriveWorkboardActionContract(snapshot) {
578367
+ if (snapshot.cards.length === 0)
578368
+ return null;
578369
+ const byId = new Map(snapshot.cards.map((card) => [card.id, card]));
578370
+ const discovery = byId.get("discover-current-state");
578371
+ const implementation2 = byId.get("implement-repair");
578372
+ const integration = byId.get("integrate-and-run");
578373
+ const verification = byId.get("verify-observed-outcome");
578374
+ const discoveryEvidence = this._workboardEvidenceCount(discovery);
578375
+ const hasImplementationEvidence = this._workboardHasEditEvidence(implementation2);
578376
+ const hasVerificationEvidence = this._workboardHasVerificationEvidence(integration);
578377
+ if (hasImplementationEvidence && !hasVerificationEvidence && integration) {
578378
+ return {
578379
+ cardId: integration.id,
578380
+ title: integration.title,
578381
+ reason: "implementation evidence exists; exercise the changed runtime path before more discovery",
578382
+ preferredTools: ["shell"]
578383
+ };
578384
+ }
578385
+ if (hasVerificationEvidence && verification) {
578386
+ return {
578387
+ cardId: verification.id,
578388
+ title: verification.title,
578389
+ reason: "verification evidence exists; reconcile outcome, todos, and blockers",
578390
+ preferredTools: ["todo_write", "task_complete"]
578391
+ };
578392
+ }
578393
+ if (discoveryEvidence > 0 && !hasImplementationEvidence && implementation2) {
578394
+ return {
578395
+ cardId: implementation2.id,
578396
+ title: implementation2.title,
578397
+ reason: "discovery evidence is already present; land the smallest concrete repair",
578398
+ preferredTools: ["file_write", "file_edit", "batch_edit", "file_patch"]
578399
+ };
578400
+ }
578401
+ const active = snapshot.cards.find((card) => card.status === "in_progress") ?? snapshot.cards.find((card) => card.status === "needs_changes") ?? snapshot.cards.find((card) => card.status === "open") ?? discovery;
578402
+ if (!active)
578403
+ return null;
578404
+ return {
578405
+ cardId: active.id,
578406
+ title: active.title,
578407
+ reason: active.id === "discover-current-state" ? "no authoritative target evidence is recorded yet" : "workboard card is the next unresolved card",
578408
+ preferredTools: active.id === "discover-current-state" ? ["file_read", "grep_search", "list_directory", "shell"] : ["file_write", "file_edit", "batch_edit", "file_patch", "shell"]
578409
+ };
578410
+ }
578411
+ _focusToolsForRequiredAction(action) {
578412
+ switch (action) {
578413
+ case "update_todos":
578414
+ return "todo_write with a changed plan/status, or a real file edit if that is the active work";
578415
+ case "read_authoritative_target":
578416
+ case "use_cached_evidence":
578417
+ return "use cached evidence or one authoritative file_read/read-only shell, then act";
578418
+ case "edit_different_target":
578419
+ return "file_write, file_edit, batch_edit, or file_patch on a different supported target";
578420
+ case "run_verification":
578421
+ return "shell running the verification/runtime command";
578422
+ case "report_blocked":
578423
+ case "report_incomplete":
578424
+ return "task_complete with the concrete blocker/incomplete evidence";
578425
+ default:
578426
+ return "choose the narrowest tool that advances the current card";
578427
+ }
578428
+ }
578429
+ _renderNextActionContract(turn) {
578430
+ const lines = ["[NEXT ACTION CONTRACT]", `turn=${turn}`];
578431
+ const focus = this._focusSupervisor?.snapshot().directive ?? null;
578432
+ if (focus) {
578433
+ lines.push(`focus_required_next_action=${focus.requiredNextAction}`);
578434
+ lines.push(`focus_valid_tools=${this._focusToolsForRequiredAction(focus.requiredNextAction)}`);
578435
+ lines.push(`focus_reason=${focus.reason}`);
578436
+ }
578437
+ const snapshot = this._currentWorkboardSnapshotForFrame();
578438
+ const action = snapshot ? this._deriveWorkboardActionContract(snapshot) : null;
578439
+ if (snapshot && action) {
578440
+ const byId = new Map(snapshot.cards.map((card) => [card.id, card]));
578441
+ const discovery = byId.get("discover-current-state");
578442
+ const implementation2 = byId.get("implement-repair");
578443
+ const integration = byId.get("integrate-and-run");
578444
+ const verification = byId.get("verify-observed-outcome");
578445
+ lines.push(`workboard_current_card=${action.cardId} (${action.title})`);
578446
+ lines.push(`workboard_preferred_tools=${action.preferredTools.join(", ")}`);
578447
+ lines.push(`workboard_reason=${action.reason}`);
578448
+ lines.push(`workboard_evidence_counts=discover:${this._workboardEvidenceCount(discovery)} implement:${this._workboardEvidenceCount(implementation2)} integrate:${this._workboardEvidenceCount(integration)} verify:${this._workboardEvidenceCount(verification)}`);
578449
+ if (this._workboardHasEditEvidence(implementation2)) {
578450
+ lines.push("implementation_state=mutation evidence is already recorded; do not treat discovery as the active phase");
578451
+ }
578452
+ }
578453
+ const todos = this.readSessionTodos() ?? [];
578454
+ if (todos.length > 0) {
578455
+ const inProgress = todos.filter((todo) => todo.status === "in_progress");
578456
+ const pending2 = todos.filter((todo) => todo.status === "pending");
578457
+ const blocked = todos.filter((todo) => todo.status === "blocked");
578458
+ lines.push(`todo_counts=in_progress:${inProgress.length} pending:${pending2.length} blocked:${blocked.length}`);
578459
+ if (inProgress[0]) {
578460
+ lines.push(`todo_current=${inProgress[0].content.slice(0, 180)}`);
578461
+ } else if (pending2.length > 0) {
578462
+ lines.push("todo_contract=mark exactly one pending todo in_progress before using the checklist as progress");
578463
+ }
578464
+ }
578465
+ if (lines.length === 2)
578466
+ return null;
578467
+ lines.push("not_progress=repeat unchanged todo_write; repeat broad discovery already represented in this frame; keep discovery active after a real mutation");
578468
+ return lines.join("\n");
578469
+ }
578470
+ _workboardTargetCardId(snapshot, toolName, result, meta = {}) {
578471
+ const byId = new Map(snapshot.cards.map((card) => [card.id, card]));
578472
+ if (this._isProjectEditTool(toolName) && this._isRealProjectMutation(toolName, result)) {
578473
+ return byId.has("implement-repair") ? "implement-repair" : null;
578474
+ }
578475
+ if (toolName === "shell" && meta.shellFilesystemMutation === true) {
578476
+ return byId.has("implement-repair") ? "implement-repair" : null;
578477
+ }
578478
+ if (toolName === "shell" || toolName === "background_run") {
578479
+ const implementation2 = byId.get("implement-repair");
578480
+ const target = this._workboardHasEditEvidence(implementation2) ? "integrate-and-run" : "discover-current-state";
578481
+ return byId.has(target) ? target : null;
578482
+ }
578483
+ if (toolName === "file_read" || toolName === "file_explore" || toolName === "list_directory" || toolName === "grep_search" || toolName === "find_files" || toolName === "web_search") {
578484
+ return byId.has("discover-current-state") ? "discover-current-state" : null;
578485
+ }
578486
+ const active = snapshot.cards.find((card) => card.status === "in_progress");
578487
+ return active?.id ?? null;
578488
+ }
578489
+ _refreshWorkboardSnapshot(snapshot) {
578490
+ const refreshed = readActiveWorkboardSnapshot(this._workboardDir(), this._sessionId) ?? snapshot;
578491
+ this._workboard = refreshed;
578492
+ return refreshed;
578493
+ }
578494
+ _advanceWorkboardForTool(snapshot, toolName, result, actor, meta = {}) {
578495
+ const dir = this._workboardDir();
578496
+ let current = snapshot;
578497
+ const byId = () => new Map(current.cards.map((card) => [card.id, card]));
578498
+ const editMutation = this._isProjectEditTool(toolName) && this._isRealProjectMutation(toolName, result) || toolName === "shell" && result?.success === true && meta.shellFilesystemMutation === true;
578499
+ try {
578500
+ if (editMutation) {
578501
+ let cards = byId();
578502
+ const discovery = cards.get("discover-current-state");
578503
+ if (discovery && discovery.status !== "verified" && discovery.evidence.length > 0) {
578504
+ const completed = completeWorkboardCard(dir, {
578505
+ runId: this._sessionId,
578506
+ cardId: discovery.id,
578507
+ actor,
578508
+ outcome: "completed",
578509
+ reason: "Implementation started after authoritative discovery evidence was recorded."
578510
+ });
578511
+ current = completed.snapshot;
578512
+ cards = byId();
578513
+ const completedDiscovery = cards.get("discover-current-state");
578514
+ if (completedDiscovery?.status === "completed") {
578515
+ const verified = completeWorkboardCard(dir, {
578516
+ runId: this._sessionId,
578517
+ cardId: completedDiscovery.id,
578518
+ actor,
578519
+ outcome: "verified",
578520
+ reason: "Discovery evidence was sufficient to select and mutate the implementation target."
578521
+ });
578522
+ current = verified.snapshot;
578523
+ cards = byId();
578524
+ }
578525
+ }
578526
+ const implementation2 = cards.get("implement-repair");
578527
+ if (implementation2 && (implementation2.status === "open" || implementation2.status === "needs_changes")) {
578528
+ current = updateWorkboardCard(dir, {
578529
+ runId: this._sessionId,
578530
+ cardId: implementation2.id,
578531
+ actor,
578532
+ updates: { status: "in_progress", lane: "worker" },
578533
+ decisionReason: "Real project mutation landed; implementation is now the active phase."
578534
+ });
578535
+ }
578536
+ }
578537
+ if (toolName === "shell" || toolName === "background_run") {
578538
+ const cards = byId();
578539
+ const implementation2 = cards.get("implement-repair");
578540
+ const integration = cards.get("integrate-and-run");
578541
+ if (integration && this._workboardHasEditEvidence(implementation2) && (integration.status === "open" || integration.status === "needs_changes")) {
578542
+ current = updateWorkboardCard(dir, {
578543
+ runId: this._sessionId,
578544
+ cardId: integration.id,
578545
+ actor,
578546
+ updates: { status: "in_progress", lane: "verification" },
578547
+ decisionReason: "Runtime or verification command ran after implementation evidence."
578548
+ });
578549
+ }
578550
+ }
578551
+ } catch {
578552
+ }
578553
+ return this._refreshWorkboardSnapshot(current);
578554
+ }
578307
578555
  /**
578308
578556
  * Record evidence from a tool call against the active workboard.
578309
578557
  * Matches by card assignee when the card is in progress.
578310
578558
  * Non-fatal: failures to attach evidence are silently caught.
578311
578559
  */
578312
- recordWorkboardToolCall(toolName, args, success, output, result) {
578560
+ recordWorkboardToolCall(toolName, args, success, output, result, meta = {}) {
578313
578561
  const board = this._workboard ?? this.getOrCreateWorkboard();
578314
578562
  if (!board)
578315
578563
  return;
578316
578564
  if (toolName === "task_complete" || toolName === "workboard_create")
578317
578565
  return;
578566
+ if (meta.repeatShortCircuit || result?.runtimeAuthored)
578567
+ return;
578568
+ if (meta.focusDecisionKind === "block_tool_call")
578569
+ return;
578318
578570
  if (result?.success === true && this._isProjectEditTool(toolName) && !this._isRealProjectMutation(toolName, result)) {
578319
578571
  return;
578320
578572
  }
578321
578573
  const dir = this._workboardDir();
578322
578574
  try {
578323
- const evidenceSummary = `${toolName} ${success ? "ok" : "fail"}`;
578575
+ const evidenceSummary = toolName === "shell" && meta.shellFilesystemMutation ? `shell filesystem mutation ${success ? "ok" : "fail"}` : `${toolName} ${success ? "ok" : "fail"}`;
578324
578576
  const rawEvidenceOutput = typeof output === "string" && output.length > 0 ? output : result?.output || result?.error || "";
578325
578577
  const evidenceContent = rawEvidenceOutput.slice(0, 1e3);
578326
578578
  const evidence = {
@@ -578332,24 +578584,23 @@ ${parts.join("\n")}
578332
578584
  ref: typeof args["path"] === "string" ? args["path"]?.slice(0, 200) : void 0
578333
578585
  };
578334
578586
  const actor = this.options.subAgent ? "sub-agent" : "agent";
578335
- for (const card of board.cards) {
578336
- if (card.status !== "in_progress")
578337
- continue;
578338
- if (card.assignee && card.assignee !== actor && card.assignee !== "any")
578339
- continue;
578340
- if (toolName === "shell" && !card.evidenceRequirements.some((r2) => r2.toLowerCase().includes("shell") || r2.toLowerCase().includes("build") || r2.toLowerCase().includes("test")))
578341
- continue;
578342
- if (toolName === "file_write" && !card.evidenceRequirements.some((r2) => r2.toLowerCase().includes("file") || r2.toLowerCase().includes("write") || r2.toLowerCase().includes("implement")))
578343
- continue;
578344
- if (toolName === "file_read" && !card.evidenceRequirements.some((r2) => r2.toLowerCase().includes("read") || r2.toLowerCase().includes("explor") || r2.toLowerCase().includes("understand")))
578345
- continue;
578346
- attachWorkboardEvidence(dir, {
578347
- runId: this._sessionId,
578348
- cardId: card.id,
578349
- actor,
578350
- evidence
578351
- });
578587
+ const advanced = this._advanceWorkboardForTool(board, toolName, result, actor, meta);
578588
+ const targetCardId = this._workboardTargetCardId(advanced, toolName, result, meta);
578589
+ if (!targetCardId)
578590
+ return;
578591
+ const targetCard = advanced.cards.find((card) => card.id === targetCardId);
578592
+ if (!targetCard)
578593
+ return;
578594
+ if (targetCard.assignee && targetCard.assignee !== actor && targetCard.assignee !== "any") {
578595
+ return;
578352
578596
  }
578597
+ attachWorkboardEvidence(dir, {
578598
+ runId: this._sessionId,
578599
+ cardId: targetCard.id,
578600
+ actor,
578601
+ evidence
578602
+ });
578603
+ this._refreshWorkboardSnapshot(advanced);
578353
578604
  } catch {
578354
578605
  }
578355
578606
  }
@@ -579814,6 +580065,21 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
579814
580065
  return false;
579815
580066
  return true;
579816
580067
  }
580068
+ _isTodoWriteNoopResult(result) {
580069
+ if (!result)
580070
+ return false;
580071
+ if (result.noop === true)
580072
+ return true;
580073
+ const text2 = String(result.output ?? result.llmContent ?? "");
580074
+ if (!text2 || !/"noop"\s*:\s*true/.test(text2))
580075
+ return false;
580076
+ try {
580077
+ const parsed = JSON.parse(text2);
580078
+ return parsed?.noop === true;
580079
+ } catch {
580080
+ return true;
580081
+ }
580082
+ }
579817
580083
  _toolEvidencePreview(result, displayOutput, max = 500, toolName) {
579818
580084
  const modelVisible = result.llmContent ?? result.output ?? displayOutput ?? "";
579819
580085
  const failurePrefix = result.success === false && result.error ? `Error: ${result.error}` : "";
@@ -582535,6 +582801,7 @@ ${chunk.content}`, {
582535
582801
  const failureBlock = this._renderRecentFailuresBlock(turn);
582536
582802
  const churnBlock = this._renderWriteChurnBlock(turn);
582537
582803
  const focusBlock = this._focusSupervisor?.renderFrame() ?? null;
582804
+ const actionContractBlock = this._renderNextActionContract(turn);
582538
582805
  const toolCacheBlock = recentToolResults ? this._renderKnowledgeBlock(recentToolResults) : null;
582539
582806
  const anchorsBlock = this.surfaceAnchors(messages2);
582540
582807
  const pprMemoryBlock = this._lastPprMemoryLines.length > 0 ? `[Associative Memory - related prior experience]
@@ -582572,6 +582839,13 @@ ${this._lastPprMemoryLines.slice(0, 5).join("\n")}` : null;
582572
582839
  createdTurn: turn,
582573
582840
  ttlTurns: 1
582574
582841
  }),
582842
+ signalFromBlock("action_contract", "turn.next-action-contract", actionContractBlock, {
582843
+ id: "next-action-contract",
582844
+ dedupeKey: "turn.next-action-contract",
582845
+ priority: PRIORITY.ACTION_CONTRACT,
582846
+ createdTurn: turn,
582847
+ ttlTurns: 1
582848
+ }),
582575
582849
  signalFromBlock("task_state", "turn.todos", todoBlock, {
582576
582850
  id: "todo-state",
582577
582851
  dedupeKey: "turn.todos",
@@ -587486,7 +587760,9 @@ Read the current file if needed, make a different concrete edit, or move to veri
587486
587760
  success: true,
587487
587761
  output: `[STOP RE-READING — you have requested this exact read ${criticDecision.hitNumber}× and you ALREADY HAVE its full content, shown again below. Do NOT read it again. Act on it: edit the file, run a verification, or call task_complete. If a previous edit failed with "old_string not found", your old_string did not match the file — copy it EXACTLY (including indentation) from the content below.]
587488
587762
 
587489
- ` + cachedResult
587763
+ ` + cachedResult,
587764
+ runtimeAuthored: true,
587765
+ noop: true
587490
587766
  };
587491
587767
  } else if (isFailedShellRepeat) {
587492
587768
  repeatShortCircuit = {
@@ -587513,7 +587789,9 @@ ${cachedResult}`,
587513
587789
  runtimeAuthored: true
587514
587790
  } : {
587515
587791
  success: true,
587516
- output: cachedResult
587792
+ output: cachedResult,
587793
+ runtimeAuthored: true,
587794
+ noop: true
587517
587795
  };
587518
587796
  }
587519
587797
  }
@@ -587847,7 +588125,7 @@ Respond with EXACTLY this structure before your next tool call:
587847
588125
  }
587848
588126
  }
587849
588127
  }
587850
- if (tc.name === "todo_write" && result.success) {
588128
+ if (tc.name === "todo_write" && result.success && !this._isTodoWriteNoopResult(result)) {
587851
588129
  if (this._progressGateActive) {
587852
588130
  this.emit({
587853
588131
  type: "status",
@@ -588430,7 +588708,7 @@ Respond with EXACTLY this structure before your next tool call:
588430
588708
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
588431
588709
  });
588432
588710
  }
588433
- if (!this._newFieldNudgeFired) {
588711
+ if (!this._newFieldNudgeFired && !this._isTodoWriteNoopResult(result)) {
588434
588712
  this._todoWritesObservedForNudge++;
588435
588713
  const _anyFieldUsed = _todosNow.some((t2) => typeof t2.verifyCommand === "string" || Array.isArray(t2.declaredArtifacts));
588436
588714
  if (this._todoWritesObservedForNudge >= 2 && !_anyFieldUsed) {
@@ -588918,7 +589196,6 @@ Evidence: ${evidencePreview}`.slice(0, 500);
588918
589196
  turn,
588919
589197
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
588920
589198
  });
588921
- this.recordWorkboardToolCall(tc.name, tc.arguments, result.success, result.output || result.error || output, result);
588922
589199
  this._taskState.toolCallCount++;
588923
589200
  if (realFileMutation) {
588924
589201
  this._lastFileWriteTurn = turn;
@@ -589047,7 +589324,8 @@ Evidence: ${evidencePreview}`.slice(0, 500);
589047
589324
  output: result.output ?? result.llmContent ?? "",
589048
589325
  error: result.error ?? "",
589049
589326
  mutated: realFileMutation || shellFilesystemMutation,
589050
- isReadLike
589327
+ isReadLike,
589328
+ noop: tc.name === "todo_write" ? this._isTodoWriteNoopResult(result) : result.noop
589051
589329
  });
589052
589330
  const focusSnapshotAfter = this._focusSupervisor?.snapshot();
589053
589331
  focusAfterToolResult = focusSnapshotAfter ?? void 0;
@@ -589094,7 +589372,12 @@ Evidence: ${evidencePreview}`.slice(0, 500);
589094
589372
  });
589095
589373
  } catch {
589096
589374
  }
589097
- if (tc.name === "todo_write") {
589375
+ this.recordWorkboardToolCall(tc.name, tc.arguments, result.success, result.output || result.error || output, result, {
589376
+ repeatShortCircuit: Boolean(repeatShortCircuit),
589377
+ focusDecisionKind: focusDecision?.kind,
589378
+ shellFilesystemMutation
589379
+ });
589380
+ if (tc.name === "todo_write" && !this._isTodoWriteNoopResult(result)) {
589098
589381
  this._lastTodoWriteTurn = turn;
589099
589382
  }
589100
589383
  if (tc.name === "file_read" || tc.name === "list_directory" || tc.name === "find_files" || tc.name === "grep_search") {
@@ -604602,12 +604885,14 @@ var init_py_embed = __esm({
604602
604885
  var listen_exports = {};
604603
604886
  __export(listen_exports, {
604604
604887
  ListenEngine: () => ListenEngine,
604888
+ asrConsensusEnabled: () => asrConsensusEnabled,
604605
604889
  ensureTranscribeCliBackground: () => ensureTranscribeCliBackground,
604606
604890
  getListenEngine: () => getListenEngine,
604607
604891
  getListenLiveState: () => getListenLiveState,
604608
604892
  isAudioPath: () => isAudioPath,
604609
604893
  isTranscribablePath: () => isTranscribablePath,
604610
604894
  isVideoPath: () => isVideoPath,
604895
+ resolveAsrConsensusModel: () => resolveAsrConsensusModel,
604611
604896
  transcribeFileViaWhisper: () => transcribeFileViaWhisper,
604612
604897
  waitForTranscribeCli: () => waitForTranscribeCli
604613
604898
  });
@@ -605012,9 +605297,25 @@ function defaultConsensusModel(primaryModel) {
605012
605297
  if (model === "large-v3" || model === "large") return "small";
605013
605298
  return "base";
605014
605299
  }
605015
- function asrConsensusEnabled() {
605016
- const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
605017
- return !(consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false");
605300
+ function resolveAsrConsensusModel(primaryModel) {
605301
+ const disabled = String(process.env["OMNIUS_ASR_DISABLE_CONSENSUS"] ?? "").trim().toLowerCase();
605302
+ if (disabled === "1" || disabled === "true" || disabled === "yes" || disabled === "on") {
605303
+ return "off";
605304
+ }
605305
+ const consensusEnv = String(
605306
+ process.env["OMNIUS_ASR_CONSENSUS"] ?? process.env["OMNIUS_ASR_CONSENSUS_MODEL"] ?? ""
605307
+ ).trim().toLowerCase();
605308
+ if (!consensusEnv) return "off";
605309
+ if (["0", "off", "false", "no", "none", "disabled"].includes(consensusEnv)) {
605310
+ return "off";
605311
+ }
605312
+ if (["1", "on", "true", "yes", "enabled", "auto"].includes(consensusEnv)) {
605313
+ return defaultConsensusModel(primaryModel);
605314
+ }
605315
+ return consensusEnv;
605316
+ }
605317
+ function asrConsensusEnabled(primaryModel = "base") {
605318
+ return resolveAsrConsensusModel(primaryModel) !== "off";
605018
605319
  }
605019
605320
  function liveAsrUseTranscribeCli() {
605020
605321
  const value2 = String(process.env["OMNIUS_ASR_USE_TRANSCRIBE_CLI"] ?? "").trim().toLowerCase();
@@ -605247,8 +605548,7 @@ var init_listen = __esm({
605247
605548
  model: this.model,
605248
605549
  lastStatus: `loading whisper ${this.model} model...`
605249
605550
  });
605250
- const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
605251
- const consensusModel = consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false" ? "off" : consensusEnv || defaultConsensusModel(this.model);
605551
+ const consensusModel = resolveAsrConsensusModel(this.model);
605252
605552
  const consensusThreshold = String(process.env["OMNIUS_ASR_CONSENSUS_THRESHOLD"] ?? "0.45");
605253
605553
  const silenceMs = String(process.env["OMNIUS_ASR_SILENCE_MS"] ?? "3000");
605254
605554
  this.process = spawn29(
@@ -605312,7 +605612,9 @@ var init_listen = __esm({
605312
605612
  updateListenLiveState({
605313
605613
  lastStatus: message2
605314
605614
  });
605315
- this.emit("status", message2);
605615
+ if (process.env["OMNIUS_ASR_VERBOSE_CONSENSUS"] === "1") {
605616
+ this.emit("status", message2);
605617
+ }
605316
605618
  }
605317
605619
  break;
605318
605620
  case "vad": {
@@ -605665,7 +605967,7 @@ var init_listen = __esm({
605665
605967
  updateListenLiveState({ phase: "error", lastStatus: "no mic capture tool (arecord/sox/ffmpeg)" });
605666
605968
  return "No microphone capture tool found. Install arecord (Linux), sox (macOS), or ffmpeg.";
605667
605969
  }
605668
- const consensusEnabled = asrConsensusEnabled();
605970
+ const consensusEnabled = asrConsensusEnabled(this.config.model);
605669
605971
  const useTranscribeCli = liveAsrUseTranscribeCli() && !consensusEnabled;
605670
605972
  const preferWhisperFallback = !useTranscribeCli;
605671
605973
  if (preferWhisperFallback) {
@@ -605959,7 +606261,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
605959
606261
  * Caller is responsible for calling .stop() when done.
605960
606262
  */
605961
606263
  async createCallTranscriber() {
605962
- const requireConsensus = asrConsensusEnabled();
606264
+ const requireConsensus = asrConsensusEnabled(this.config.model);
605963
606265
  const useTranscribeCli = liveAsrUseTranscribeCli() && !requireConsensus;
605964
606266
  let tc = useTranscribeCli ? await this.loadTranscribeCli() : null;
605965
606267
  if (useTranscribeCli && !tc && _bgInstallPromise) {
@@ -609467,7 +609769,7 @@ var init_live_sensors = __esm({
609467
609769
  inferEnabled: true,
609468
609770
  clipEnabled: true,
609469
609771
  audioEnabled: true,
609470
- asrEnabled: true,
609772
+ asrEnabled: options2.asr ?? true,
609471
609773
  audioAnalysisEnabled: true,
609472
609774
  videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs, LOW_LATENCY_VIDEO_INTERVAL_MS),
609473
609775
  audioIntervalMs: options2.lowLatency === false ? this.config.audioIntervalMs : Math.min(this.config.audioIntervalMs, LOW_LATENCY_AUDIO_INTERVAL_MS)
@@ -610051,7 +610353,7 @@ ${output}`).join("\n\n");
610051
610353
  transcript = cleanPreview(liveAsr.lastPartialTranscript, 1600);
610052
610354
  asrBackend = [liveAsr.backend, liveAsr.model, "streaming-partial"].filter(Boolean).join(" ");
610053
610355
  } else if (liveAsr.phase === "idle") {
610054
- audioErrors.push("Streaming ASR is not running. Start /live run or /listen to enable CUDA Whisper streaming.");
610356
+ audioErrors.push("Streaming ASR is not active yet; live sound analysis is continuing without a fresh transcript.");
610055
610357
  } else if (liveAsr.lastStatus) {
610056
610358
  audioErrors.push(`Streaming ASR status: ${liveAsr.lastStatus}`);
610057
610359
  }
@@ -661253,11 +661555,24 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false, speechEnabledOverr
661253
661555
  }
661254
661556
  }
661255
661557
  async function ensureLiveStreamingAsr(ctx3, manager, force = false) {
661256
- if (!force && !manager.getConfig().asrEnabled) return;
661257
- if (!ctx3.listenStart) return;
661258
- ctx3.listenSetMode?.("auto");
661259
- const msg = await ctx3.listenStart("live");
661260
- if (!/already active/i.test(msg)) renderInfo(`Live ASR: ${msg}`);
661558
+ if (!force && !manager.getConfig().asrEnabled) return Boolean(ctx3.listenIsActive?.());
661559
+ if (!ctx3.listenStart) {
661560
+ renderWarning("Live ASR auto-start is unavailable in this command context; live audio analysis will continue without transcript intake.");
661561
+ return false;
661562
+ }
661563
+ try {
661564
+ ctx3.listenSetMode?.("auto");
661565
+ const msg = await ctx3.listenStart("live");
661566
+ const active = Boolean(ctx3.listenIsActive?.()) || /already active|listening with|already listening/i.test(msg);
661567
+ if (!/already active/i.test(msg)) {
661568
+ const render2 = active ? renderInfo : renderWarning;
661569
+ render2(`${active ? "Live ASR" : "Live ASR startup"}: ${msg}`);
661570
+ }
661571
+ return active;
661572
+ } catch (err) {
661573
+ renderWarning(`Live ASR startup failed: ${err instanceof Error ? err.message : String(err)}`);
661574
+ return false;
661575
+ }
661261
661576
  }
661262
661577
  async function releaseLiveStreamingAsr(ctx3) {
661263
661578
  try {
@@ -661514,8 +661829,8 @@ async function handleLiveCommand(ctx3, arg) {
661514
661829
  renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
661515
661830
  }
661516
661831
  await ensureLiveInventory(manager);
661517
- await ensureLiveStreamingAsr(ctx3, manager, true);
661518
- renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true, speech: effectiveAgent && Boolean(ctx3.voiceSpeak) }));
661832
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
661833
+ renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true, speech: effectiveAgent && Boolean(ctx3.voiceSpeak), asr: asrStarted }));
661519
661834
  return;
661520
661835
  }
661521
661836
  if (sub2 === "chat" || sub2 === "voicechat" || sub2 === "voice") {
@@ -661616,12 +661931,14 @@ async function handleLiveCommand(ctx3, arg) {
661616
661931
  if (sub2 === "asr") {
661617
661932
  const cfg = manager.getConfig();
661618
661933
  const enabled2 = setBool(value2, !cfg.asrEnabled);
661619
- manager.configure({
661620
- audioEnabled: true,
661621
- asrEnabled: enabled2
661622
- });
661623
- if (enabled2) await ensureLiveStreamingAsr(ctx3, manager);
661624
- else await releaseLiveStreamingAsr(ctx3);
661934
+ if (enabled2) {
661935
+ manager.configure({ audioEnabled: true, asrEnabled: false });
661936
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
661937
+ manager.configure({ asrEnabled: asrStarted });
661938
+ } else {
661939
+ manager.configure({ audioEnabled: true, asrEnabled: false });
661940
+ await releaseLiveStreamingAsr(ctx3);
661941
+ }
661625
661942
  renderInfo(formatLiveStatus(manager.getSnapshot()));
661626
661943
  return;
661627
661944
  }
@@ -661674,8 +661991,9 @@ async function handleLiveCommand(ctx3, arg) {
661674
661991
  }
661675
661992
  if (sub2 === "sample" || sub2 === "hear") {
661676
661993
  await ensureLiveInventory(manager);
661677
- manager.configure({ audioEnabled: true, asrEnabled: true, audioAnalysisEnabled: true });
661678
- await ensureLiveStreamingAsr(ctx3, manager, true);
661994
+ manager.configure({ audioEnabled: true, asrEnabled: false, audioAnalysisEnabled: false });
661995
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
661996
+ manager.configure({ asrEnabled: asrStarted, audioAnalysisEnabled: true });
661679
661997
  renderInfo("Sampling live audio...");
661680
661998
  renderInfo(await manager.sampleAudioNow());
661681
661999
  return;
@@ -661911,8 +662229,8 @@ async function showLiveMenu(ctx3, manager) {
661911
662229
  await ensureLiveInventory(manager);
661912
662230
  if (!manager.isRunning()) {
661913
662231
  attachLiveSinks(ctx3, manager, false);
661914
- await ensureLiveStreamingAsr(ctx3, manager, true);
661915
- renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
662232
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
662233
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false, asr: asrStarted }));
661916
662234
  }
661917
662235
  while (true) {
661918
662236
  await refreshLiveResourceModelSnapshot().catch(() => void 0);
@@ -662070,19 +662388,21 @@ async function showLiveMenu(ctx3, manager) {
662070
662388
  case "toggle-video":
662071
662389
  manager.configure({ videoEnabled: !cfg.videoEnabled });
662072
662390
  continue;
662073
- case "run-live":
662391
+ case "run-live": {
662074
662392
  attachLiveSinks(ctx3, manager, false);
662075
- await ensureLiveStreamingAsr(ctx3, manager, true);
662076
- renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
662393
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
662394
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false, asr: asrStarted }));
662077
662395
  continue;
662078
- case "run-agent":
662396
+ }
662397
+ case "run-agent": {
662079
662398
  attachLiveSinks(ctx3, manager, true);
662080
662399
  if (!ctx3.startBackgroundPrompt) {
662081
662400
  renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
662082
662401
  }
662083
- await ensureLiveStreamingAsr(ctx3, manager, true);
662084
- renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true, speech: Boolean(ctx3.startBackgroundPrompt && ctx3.voiceSpeak) }));
662402
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
662403
+ renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true, speech: Boolean(ctx3.startBackgroundPrompt && ctx3.voiceSpeak), asr: asrStarted }));
662085
662404
  continue;
662405
+ }
662086
662406
  case "run-chat":
662087
662407
  await startLiveVoicechat(ctx3, manager);
662088
662408
  continue;
@@ -662099,9 +662419,14 @@ async function showLiveMenu(ctx3, manager) {
662099
662419
  manager.configure({ videoEnabled: true, clipEnabled: !cfg.clipEnabled });
662100
662420
  continue;
662101
662421
  case "toggle-asr":
662102
- manager.configure({ audioEnabled: true, asrEnabled: !cfg.asrEnabled });
662103
- if (!cfg.asrEnabled) await ensureLiveStreamingAsr(ctx3, manager);
662104
- else await releaseLiveStreamingAsr(ctx3);
662422
+ if (!cfg.asrEnabled) {
662423
+ manager.configure({ audioEnabled: true, asrEnabled: false });
662424
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
662425
+ manager.configure({ asrEnabled: asrStarted });
662426
+ } else {
662427
+ manager.configure({ audioEnabled: true, asrEnabled: false });
662428
+ await releaseLiveStreamingAsr(ctx3);
662429
+ }
662105
662430
  continue;
662106
662431
  case "toggle-sounds":
662107
662432
  manager.configure({ audioEnabled: true, audioAnalysisEnabled: !cfg.audioAnalysisEnabled });
@@ -662149,12 +662474,14 @@ Previewing microphone activity...`);
662149
662474
  renderInfo("Sampling camera inference...");
662150
662475
  renderInfo(await manager.sampleVideoNow(true));
662151
662476
  continue;
662152
- case "sample-audio":
662153
- manager.configure({ audioEnabled: true, asrEnabled: true, audioAnalysisEnabled: true });
662154
- await ensureLiveStreamingAsr(ctx3, manager, true);
662477
+ case "sample-audio": {
662478
+ manager.configure({ audioEnabled: true, asrEnabled: false, audioAnalysisEnabled: false });
662479
+ const asrStarted = await ensureLiveStreamingAsr(ctx3, manager, true);
662480
+ manager.configure({ asrEnabled: asrStarted, audioAnalysisEnabled: true });
662155
662481
  renderInfo("Sampling live audio...");
662156
662482
  renderInfo(await manager.sampleAudioNow());
662157
662483
  continue;
662484
+ }
662158
662485
  case "listen-mode":
662159
662486
  if (ctx3.listenToggle) {
662160
662487
  ctx3.listenSetMode?.("auto");
@@ -702138,6 +702465,25 @@ function buildSyntheticContext(config, repoRoot) {
702138
702465
  renderError(`voice model ${TUI_ONLY_HINT}`);
702139
702466
  return "";
702140
702467
  },
702468
+ listenStart: async (owner = "live") => {
702469
+ const engine = getDaemonListenEngine();
702470
+ const available = await engine.isAvailable();
702471
+ if (!available) {
702472
+ return "ASR runtime unavailable. Omnius attempted managed Whisper setup; check ~/.omnius/runtimes/asr and CUDA PyTorch.";
702473
+ }
702474
+ engine.setMode("auto");
702475
+ return engine.acquire(owner);
702476
+ },
702477
+ listenRelease: async (owner = "live") => {
702478
+ const engine = getDaemonListenEngine();
702479
+ return engine.release(owner);
702480
+ },
702481
+ listenIsActive: () => getDaemonListenEngine().isActive,
702482
+ listenSetMode: (mode) => {
702483
+ const engine = getDaemonListenEngine();
702484
+ engine.setMode(mode);
702485
+ return mode === "auto" ? "Auto mode: transcriptions auto-submit after 3s silence" : "Manual mode: press Enter to submit transcriptions (auto-submit off)";
702486
+ },
702141
702487
  getColors: () => colorsEnabled,
702142
702488
  setColors: (enabled2) => {
702143
702489
  colorsEnabled = enabled2;
@@ -738430,13 +738776,14 @@ This is an independent background session started from /background.`
738430
738776
  headerBtnQueue = command;
738431
738777
  return;
738432
738778
  }
738433
- const commandToRun = command === "live" ? isLiveSensorActiveForRepo(repoRoot) ? "live stop" : "live run" : command;
738779
+ const normalizedHeaderCommand = command.replace(/^\/+/, "");
738780
+ const commandToRun = normalizedHeaderCommand === "live" ? isLiveSensorActiveForRepo(repoRoot) ? "live stop" : "live run" : command;
738434
738781
  if (headerBtnActive && headerBtnActive !== command) {
738435
738782
  headerBtnQueue = command;
738436
738783
  return;
738437
738784
  }
738438
738785
  if (headerBtnActive === command) {
738439
- if (command === "live") headerBtnQueue = command;
738786
+ if (normalizedHeaderCommand === "live") headerBtnQueue = command;
738440
738787
  return;
738441
738788
  }
738442
738789
  headerBtnActive = command;
@@ -740566,10 +740913,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
740566
740913
  sharedTranscriber: null
740567
740914
  };
740568
740915
  const engine = getListenEngine();
740569
- const callConsensusRequired = (() => {
740570
- const env2 = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
740571
- return !(env2 === "0" || env2 === "off" || env2 === "false");
740572
- })();
740916
+ const callConsensusRequired = asrConsensusEnabled();
740573
740917
  const recentCallFinals = [];
740574
740918
  const normalizeCallTranscript = (value2) => value2.toLowerCase().replace(/[^\p{L}\p{N}\s']/gu, " ").replace(/\s+/g, " ").trim();
740575
740919
  const isLikelyCallAsrHallucination = (normalized) => /^(thanks|thank you) for watching(?: this video)?$/.test(normalized) || /^thanks for watching and/.test(normalized) || /\bdon't forget to (like|subscribe)\b/.test(normalized);
@@ -14,8 +14,8 @@ Architecture (EGG voice/whisper lineage):
14
14
  (USB 2886:0018), the XMOS DSP's SPEECHDETECTED register gates utterances,
15
15
  on-chip echo cancellation is enabled, and the DOA angle is attached to
16
16
  transcripts. Falls back to adaptive energy VAD otherwise.
17
- - Dual-model consensus: a second whisper model transcribes finalized
18
- utterances in parallel; transcripts are only emitted when both agree.
17
+ - Optional dual-model consensus: a second whisper model can validate
18
+ finalized utterances when explicitly enabled.
19
19
 
20
20
  Protocol:
21
21
  stdin — raw PCM16 audio (16kHz, mono, 16-bit signed LE)
@@ -58,6 +58,23 @@ SAMPLE_WIDTH = 2 # 16-bit
58
58
  BLOCK_SECONDS = 0.2
59
59
  BLOCK_SAMPLES = int(SAMPLE_RATE * BLOCK_SECONDS)
60
60
 
61
+
62
+ def env_float(name: str, default: float) -> float:
63
+ raw = os.environ.get(name, "").strip()
64
+ if not raw:
65
+ return default
66
+ try:
67
+ return float(raw)
68
+ except ValueError:
69
+ return default
70
+
71
+
72
+ def env_bool(name: str, default: bool = False) -> bool:
73
+ raw = os.environ.get(name, "").strip().lower()
74
+ if not raw:
75
+ return default
76
+ return raw in ("1", "true", "yes", "on", "enabled")
77
+
61
78
  # ---------------------------------------------------------------------------
62
79
  # Output helpers (JSON lines to stdout)
63
80
  # ---------------------------------------------------------------------------
@@ -450,7 +467,7 @@ def main():
450
467
  parser.add_argument("--chunk-seconds", type=float, default=3, help=argparse.SUPPRESS)
451
468
  parser.add_argument("--window-seconds", type=float, default=10, help=argparse.SUPPRESS)
452
469
  parser.add_argument("--language", default=None, help="Language code (e.g. en, es, fr). Auto-detect if omitted.")
453
- parser.add_argument("--consensus-model", default="tiny",
470
+ parser.add_argument("--consensus-model", default="off",
454
471
  help="Second whisper model run in parallel on finalized utterances; transcripts are only emitted when both models agree. 'off' disables.")
455
472
  parser.add_argument("--consensus-threshold", type=float, default=0.55,
456
473
  help="Token similarity (0-1) required between the two models' transcripts.")
@@ -460,6 +477,18 @@ def main():
460
477
  help="Audio kept from before speech onset.")
461
478
  parser.add_argument("--max-utterance-seconds", type=float, default=22,
462
479
  help="Force-finalize utterances longer than this.")
480
+ parser.add_argument("--partials", action="store_true", default=env_bool("OMNIUS_ASR_PARTIALS", False),
481
+ help="Emit interim Whisper transcripts during long utterances. Disabled by default to avoid silence/noise hallucinations.")
482
+ parser.add_argument("--min-speech-ms", type=float, default=env_float("OMNIUS_ASR_MIN_SPEECH_MS", 450.0),
483
+ help="Minimum VAD-positive speech duration required before an utterance is sent to Whisper.")
484
+ parser.add_argument("--start-rms", type=float, default=env_float("OMNIUS_ASR_START_RMS", 0.0025),
485
+ help="Minimum block RMS required for software VAD speech start.")
486
+ parser.add_argument("--energy-ratio", type=float, default=env_float("OMNIUS_ASR_ENERGY_RATIO", 3.5),
487
+ help="Software VAD speech gate as a multiple of the adaptive noise floor.")
488
+ parser.add_argument("--min-utterance-rms", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_RMS", 0.003),
489
+ help="Minimum loud-block RMS required before final transcription.")
490
+ parser.add_argument("--min-utterance-peak", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_PEAK", 0.015),
491
+ help="Minimum utterance peak amplitude required when RMS is low.")
463
492
  args = parser.parse_args()
464
493
 
465
494
  import whisper
@@ -543,6 +572,9 @@ def main():
543
572
  utterance = [] # list of float32 blocks while capturing
544
573
  capturing = False
545
574
  silence_run = 0
575
+ speech_blocks = 0
576
+ utterance_rms_values = []
577
+ utterance_peak = 0.0
546
578
  noise_floor = None
547
579
  last_partial_at = 0.0
548
580
  last_final_text = ""
@@ -558,7 +590,7 @@ def main():
558
590
  noise_floor = 0.7 * noise_floor + 0.3 * block_rms # drop fast
559
591
  else:
560
592
  noise_floor = 0.995 * noise_floor + 0.005 * block_rms # creep up slowly
561
- gate = max(1.5e-4, noise_floor * 2.5) # ≥ ~8dB above floor
593
+ gate = max(float(args.start_rms), noise_floor * float(args.energy_ratio))
562
594
  return block_rms >= gate
563
595
 
564
596
  def transcribe_partial(samples):
@@ -574,9 +606,31 @@ def main():
574
606
  except Exception as e:
575
607
  emit_error(f"Transcription error: {e}")
576
608
 
577
- def transcribe_final(samples):
578
- nonlocal last_final_text
609
+ def utterance_quality_ok(samples, speech_block_count, rms_values, peak) -> bool:
579
610
  if len(samples) < int(0.4 * SAMPLE_RATE):
611
+ return False
612
+ speech_ms = speech_block_count * BLOCK_SECONDS * 1000.0
613
+ if speech_ms < float(args.min_speech_ms):
614
+ if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
615
+ emit_status(f"Dropped short VAD blob before Whisper: speech_ms={speech_ms:.0f}")
616
+ return False
617
+ if rms_values:
618
+ ordered = sorted(float(v) for v in rms_values)
619
+ loud_half = ordered[len(ordered) // 2:] or ordered
620
+ loud_rms = loud_half[len(loud_half) // 2]
621
+ else:
622
+ loud_rms = float(np.sqrt(np.mean(samples ** 2))) if len(samples) else 0.0
623
+ if loud_rms < float(args.min_utterance_rms) and peak < float(args.min_utterance_peak):
624
+ if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
625
+ emit_status(
626
+ f"Dropped low-energy VAD blob before Whisper: loud_rms={loud_rms:.5f} peak={peak:.5f}"
627
+ )
628
+ return False
629
+ return True
630
+
631
+ def transcribe_final(samples, speech_block_count=0, rms_values=None, peak=0.0):
632
+ nonlocal last_final_text
633
+ if not utterance_quality_ok(samples, speech_block_count, rms_values or [], peak):
580
634
  return
581
635
  samples = amplify(samples)
582
636
  try:
@@ -612,7 +666,9 @@ def main():
612
666
  last_final_text = primary_text
613
667
  emit_transcript(primary_text, is_final=True, doa=primary_doa, consensus=True)
614
668
  except Exception as e:
615
- emit_error(f"Consensus validation error: {e}")
669
+ emit_consensus_rejected(primary_text, "", "validator_error")
670
+ if env_bool("OMNIUS_ASR_VERBOSE_CONSENSUS", False):
671
+ emit_status(f"Consensus validation error: {e}")
616
672
 
617
673
  threading.Thread(
618
674
  target=validate_consensus,
@@ -664,12 +720,18 @@ def main():
664
720
  if speech:
665
721
  capturing = True
666
722
  silence_run = 0
723
+ speech_blocks = 1
724
+ utterance_rms_values = [block_rms]
725
+ utterance_peak = float(np.max(np.abs(block))) if len(block) else 0.0
667
726
  utterance = list(preroll)
668
727
  preroll.clear()
669
728
  continue
670
729
 
671
730
  utterance.append(block)
731
+ utterance_rms_values.append(block_rms)
732
+ utterance_peak = max(utterance_peak, float(np.max(np.abs(block))) if len(block) else 0.0)
672
733
  if speech:
734
+ speech_blocks += 1
673
735
  silence_run = 0
674
736
  else:
675
737
  silence_run += 1
@@ -677,11 +739,17 @@ def main():
677
739
  utter_samples = sum(len(b) for b in utterance)
678
740
  if silence_run >= finalize_blocks or utter_samples >= max_utter_samples:
679
741
  samples = np.concatenate(utterance)
742
+ final_speech_blocks = speech_blocks
743
+ final_rms_values = list(utterance_rms_values)
744
+ final_peak = utterance_peak
680
745
  capturing = False
681
746
  utterance = []
682
747
  silence_run = 0
683
- transcribe_final(samples)
684
- elif utter_samples >= int(3.5 * SAMPLE_RATE) and now - last_partial_at >= 2.5:
748
+ speech_blocks = 0
749
+ utterance_rms_values = []
750
+ utterance_peak = 0.0
751
+ transcribe_final(samples, final_speech_blocks, final_rms_values, final_peak)
752
+ elif args.partials and utter_samples >= int(3.5 * SAMPLE_RATE) and now - last_partial_at >= 2.5:
685
753
  last_partial_at = now
686
754
  transcribe_partial(np.concatenate(utterance))
687
755
  except KeyboardInterrupt:
@@ -690,7 +758,12 @@ def main():
690
758
  # EOF: finalize any in-flight utterance.
691
759
  if utterance:
692
760
  try:
693
- transcribe_final(np.concatenate(utterance))
761
+ transcribe_final(
762
+ np.concatenate(utterance),
763
+ speech_blocks,
764
+ list(utterance_rms_values),
765
+ utterance_peak,
766
+ )
694
767
  except Exception:
695
768
  pass
696
769
  if tuning is not None:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.444",
3
+ "version": "1.0.446",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.444",
9
+ "version": "1.0.446",
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.444",
3
+ "version": "1.0.446",
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",