omnius 1.0.114 → 1.0.115

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
@@ -619825,18 +619825,167 @@ ${retryText}`,
619825
619825
  * in-flight promise, and on completion fire any queued trailing call.
619826
619826
  */
619827
619827
  startCoalescedTelegramRouterCall(sessionKey, msg, toolContext) {
619828
- const promise = this.inferTelegramInteractionDecision(msg, toolContext);
619828
+ const HARD_DEADLINE_MS = 18e4;
619829
+ const inner = this.inferTelegramInteractionDecision(msg, toolContext);
619830
+ const promise = new Promise((resolve52, reject) => {
619831
+ let settled = false;
619832
+ const guard = setTimeout(() => {
619833
+ if (settled) return;
619834
+ settled = true;
619835
+ reject(new Error("router-coalescer: hard deadline exceeded (180s); inner inference did not settle"));
619836
+ }, HARD_DEADLINE_MS);
619837
+ if (typeof guard.unref === "function") guard.unref();
619838
+ inner.then(
619839
+ (v) => {
619840
+ if (settled) return;
619841
+ settled = true;
619842
+ clearTimeout(guard);
619843
+ resolve52(v);
619844
+ },
619845
+ (e2) => {
619846
+ if (settled) return;
619847
+ settled = true;
619848
+ clearTimeout(guard);
619849
+ reject(e2);
619850
+ }
619851
+ );
619852
+ });
619829
619853
  this.telegramRouterSessionState.set(sessionKey, { inFlight: promise });
619830
619854
  const onSettled = () => {
619831
- const state = this.telegramRouterSessionState.get(sessionKey);
619832
- this.telegramRouterSessionState.delete(sessionKey);
619855
+ let state;
619856
+ try {
619857
+ state = this.telegramRouterSessionState.get(sessionKey);
619858
+ this.telegramRouterSessionState.delete(sessionKey);
619859
+ } catch {
619860
+ state = void 0;
619861
+ }
619833
619862
  if (!state?.trailing) return;
619834
619863
  const { msg: nextMsg, toolContext: nextCtx, resolve: resolve52, reject } = state.trailing;
619835
- this.startCoalescedTelegramRouterCall(sessionKey, nextMsg, nextCtx).then(resolve52, reject);
619864
+ try {
619865
+ this.startCoalescedTelegramRouterCall(sessionKey, nextMsg, nextCtx).then(resolve52, reject);
619866
+ } catch (err) {
619867
+ reject(err);
619868
+ }
619836
619869
  };
619837
619870
  promise.then(onSettled, onSettled);
619838
619871
  return promise;
619839
619872
  }
619873
+ /**
619874
+ * Forcibly cancel every in-flight + trailing router-coalescer entry.
619875
+ * Used on bridge stop() and by the watchdog if it detects the coalescer
619876
+ * map has grown unboundedly. Rejects every queued caller cleanly so they
619877
+ * surface the cancellation rather than waiting forever.
619878
+ */
619879
+ cancelTelegramRouterSessionState(reason) {
619880
+ const err = new Error(`router-coalescer cancelled: ${reason}`);
619881
+ for (const [, state] of this.telegramRouterSessionState) {
619882
+ if (state.trailing) {
619883
+ try {
619884
+ state.trailing.reject(err);
619885
+ } catch {
619886
+ }
619887
+ }
619888
+ }
619889
+ this.telegramRouterSessionState.clear();
619890
+ }
619891
+ // ─────────────────────────────────────────────────────────────────
619892
+ // Sub-agent staleness watchdog
619893
+ // ─────────────────────────────────────────────────────────────────
619894
+ /** Interval handle for the periodic stale-sub-agent reaper. */
619895
+ telegramSubAgentWatchdogTimer = null;
619896
+ /**
619897
+ * Maximum wall-clock time a sub-agent may go without a visible-edit
619898
+ * progress event before the watchdog declares it stale and tears it
619899
+ * down. Tuned to be comfortably longer than the slowest healthy turn
619900
+ * (which is bounded by request_timeout = 5-15min per turn) but short
619901
+ * enough that a wedged sub-agent doesn't pin a chat for an entire day.
619902
+ *
619903
+ * Override with env var OMNIUS_TG_SUBAGENT_MAX_IDLE_MS for ops tuning.
619904
+ */
619905
+ telegramSubAgentMaxIdleMs() {
619906
+ const raw = Number.parseInt(process.env["OMNIUS_TG_SUBAGENT_MAX_IDLE_MS"] ?? "", 10);
619907
+ if (Number.isFinite(raw) && raw >= 3e4 && raw <= 36e5) return raw;
619908
+ return 6e5;
619909
+ }
619910
+ /** Watchdog tick period — checked every 30s. */
619911
+ telegramSubAgentWatchdogIntervalMs() {
619912
+ return 3e4;
619913
+ }
619914
+ /**
619915
+ * Start the periodic stale-sub-agent reaper. Idempotent — safe to call
619916
+ * multiple times (no-op if already running). Stopped by stop() and on
619917
+ * SIGTERM via the cleanup chain.
619918
+ */
619919
+ startTelegramSubAgentWatchdog() {
619920
+ if (this.telegramSubAgentWatchdogTimer) return;
619921
+ const tick = () => {
619922
+ try {
619923
+ this.reapStaleTelegramSubAgents();
619924
+ } catch (err) {
619925
+ this.tuiWrite(() => renderTelegramSubAgentError(
619926
+ "watchdog",
619927
+ `tick failed: ${err instanceof Error ? err.message : String(err)}`
619928
+ ));
619929
+ }
619930
+ };
619931
+ this.telegramSubAgentWatchdogTimer = setInterval(tick, this.telegramSubAgentWatchdogIntervalMs());
619932
+ if (typeof this.telegramSubAgentWatchdogTimer.unref === "function") {
619933
+ this.telegramSubAgentWatchdogTimer.unref();
619934
+ }
619935
+ }
619936
+ /** Stop the periodic stale-sub-agent reaper. */
619937
+ stopTelegramSubAgentWatchdog() {
619938
+ if (this.telegramSubAgentWatchdogTimer) {
619939
+ clearInterval(this.telegramSubAgentWatchdogTimer);
619940
+ this.telegramSubAgentWatchdogTimer = null;
619941
+ }
619942
+ }
619943
+ /**
619944
+ * One watchdog pass: walk the sub-agent map; for each entry where the
619945
+ * last visible-edit progress event is older than maxIdle AND no completion
619946
+ * boundary has been seen, abort the runner and remove the entry. This is
619947
+ * the load-bearing fix for the runaway-sub-agent steady-state leak: a
619948
+ * runner that hangs (qwen3 think-stall, Ollama TCP wedge, lost stream)
619949
+ * otherwise pins the chat forever, because the finally{} in runSubAgent
619950
+ * never fires.
619951
+ */
619952
+ reapStaleTelegramSubAgents() {
619953
+ const maxIdleMs = this.telegramSubAgentMaxIdleMs();
619954
+ const now = Date.now();
619955
+ const stale = [];
619956
+ for (const [sessionKey, agent] of this.subAgents) {
619957
+ if (agent.aborted) continue;
619958
+ const idle = agent.lastEditMs > 0 ? now - agent.lastEditMs : 0;
619959
+ if (idle <= maxIdleMs) continue;
619960
+ if (agent.completionBoundarySeen) continue;
619961
+ stale.push(sessionKey);
619962
+ }
619963
+ for (const sessionKey of stale) {
619964
+ const agent = this.subAgents.get(sessionKey);
619965
+ if (!agent) continue;
619966
+ agent.aborted = true;
619967
+ if (agent.typingInterval) {
619968
+ clearInterval(agent.typingInterval);
619969
+ agent.typingInterval = null;
619970
+ }
619971
+ try {
619972
+ agent.runner?.abort?.();
619973
+ } catch {
619974
+ }
619975
+ this.subAgents.delete(sessionKey);
619976
+ this.refreshActiveTelegramInteractionCount();
619977
+ this.tuiWrite(() => renderTelegramSubAgentEvent(
619978
+ agent.username,
619979
+ `watchdog: aborted stale sub-agent (idle ${Math.round((now - agent.lastEditMs) / 1e3)}s without completion)`
619980
+ ));
619981
+ this.subAgentViewCallbacks?.onWrite(
619982
+ agent.viewId,
619983
+ `watchdog: sub-agent retired after ${Math.round((now - agent.lastEditMs) / 1e3)}s without a progress event`
619984
+ );
619985
+ this.subAgentViewCallbacks?.onStatus(agent.viewId, "failed");
619986
+ this.subAgentViewCallbacks?.onComplete(agent.viewId);
619987
+ }
619988
+ }
619840
619989
  async inferTelegramInteractionDecision(msg, toolContext) {
619841
619990
  const config = this.agentConfig;
619842
619991
  const forcedRoute = this.interactionMode === "chat" || this.interactionMode === "action" ? this.interactionMode : null;
@@ -620434,6 +620583,7 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
620434
620583
  this.polling = true;
620435
620584
  this.pollFatalNotified = false;
620436
620585
  this.abortController = new AbortController();
620586
+ this.startTelegramSubAgentWatchdog();
620437
620587
  await this.prepareTelegramLongPolling();
620438
620588
  try {
620439
620589
  mkdirSync66(this.mediaCacheDir, { recursive: true });
@@ -620509,7 +620659,13 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
620509
620659
  for (const [, agent] of this.subAgents) {
620510
620660
  agent.aborted = true;
620511
620661
  if (agent.typingInterval) clearInterval(agent.typingInterval);
620662
+ try {
620663
+ agent.runner?.abort?.();
620664
+ } catch {
620665
+ }
620512
620666
  }
620667
+ this.stopTelegramSubAgentWatchdog();
620668
+ this.cancelTelegramRouterSessionState("bridge stop");
620513
620669
  if (this.telegramSqliteDb && this.telegramSqliteDb !== false) {
620514
620670
  try {
620515
620671
  this.telegramSqliteDb.close();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.114",
3
+ "version": "1.0.115",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.114",
9
+ "version": "1.0.115",
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.114",
3
+ "version": "1.0.115",
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",