@agent-relay/wrapper 0.1.0 → 2.0.2

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/src/shared.ts CHANGED
@@ -7,6 +7,47 @@
7
7
 
8
8
  import type { SyncMeta } from '@agent-relay/protocol/types';
9
9
 
10
+ /**
11
+ * Message priority levels for queue ordering.
12
+ * Lower numbers = higher priority (processed first).
13
+ */
14
+ export const MESSAGE_PRIORITY = {
15
+ /** System-critical messages (sync ACKs, errors) - skip idle wait */
16
+ URGENT: 0,
17
+ /** Time-sensitive messages (user requests) - reduced idle wait */
18
+ HIGH: 1,
19
+ /** Normal agent-to-agent messages */
20
+ NORMAL: 2,
21
+ /** Batch/background messages - can wait longer */
22
+ LOW: 3,
23
+ } as const;
24
+
25
+ export type MessagePriority = typeof MESSAGE_PRIORITY[keyof typeof MESSAGE_PRIORITY];
26
+
27
+ /**
28
+ * Get priority from importance value or default to NORMAL.
29
+ * Maps importance number to priority level.
30
+ */
31
+ export function getPriorityFromImportance(importance?: number): MessagePriority {
32
+ if (importance === undefined) return MESSAGE_PRIORITY.NORMAL;
33
+ if (importance >= 90) return MESSAGE_PRIORITY.URGENT;
34
+ if (importance >= 70) return MESSAGE_PRIORITY.HIGH;
35
+ if (importance >= 30) return MESSAGE_PRIORITY.NORMAL;
36
+ return MESSAGE_PRIORITY.LOW;
37
+ }
38
+
39
+ /**
40
+ * Sort messages by priority (lower number = higher priority).
41
+ * Stable sort - maintains order within same priority.
42
+ */
43
+ export function sortByPriority(messages: QueuedMessage[]): QueuedMessage[] {
44
+ return [...messages].sort((a, b) => {
45
+ const priorityA = getPriorityFromImportance(a.importance);
46
+ const priorityB = getPriorityFromImportance(b.importance);
47
+ return priorityA - priorityB;
48
+ });
49
+ }
50
+
10
51
  /**
11
52
  * Message queued for injection into an agent's terminal
12
53
  */
@@ -50,6 +50,8 @@ import {
50
50
  INJECTION_CONSTANTS,
51
51
  CLI_QUIRKS,
52
52
  AdaptiveThrottle,
53
+ sortByPriority,
54
+ getPriorityFromImportance,
53
55
  } from './shared.js';
54
56
  import { getTmuxPanePid } from './idle-detector.js';
55
57
  import { DEFAULT_TMUX_WRAPPER_CONFIG } from '@agent-relay/config/relay-config';
@@ -1575,18 +1577,32 @@ export class TmuxWrapper extends BaseWrapper {
1575
1577
  /**
1576
1578
  * Check if we should inject a message.
1577
1579
  * Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
1580
+ * Processes messages by priority (urgent first).
1578
1581
  */
1579
1582
  private checkForInjectionOpportunity(): void {
1580
1583
  if (this.messageQueue.length === 0) return;
1581
1584
  if (this.isInjecting) return;
1582
1585
  if (!this.running) return;
1583
1586
 
1587
+ // Sort queue by priority before processing (urgent messages first)
1588
+ if (this.messageQueue.length > 1) {
1589
+ this.messageQueue = sortByPriority(this.messageQueue);
1590
+ }
1591
+
1592
+ // Check the priority of the next message
1593
+ const nextMsg = this.messageQueue[0];
1594
+ const priority = getPriorityFromImportance(nextMsg?.importance);
1595
+
1584
1596
  // Use universal idle detector for more reliable detection (inherited from BaseWrapper)
1585
1597
  const idleResult = this.checkIdleForInjection();
1586
1598
 
1587
- if (!idleResult.isIdle) {
1588
- // Not idle yet, retry later
1589
- const retryMs = this.config.injectRetryMs ?? 500;
1599
+ // Urgent messages (priority 0) can proceed with lower idle confidence
1600
+ const idleThreshold = priority === 0 ? 0.5 : 0.7;
1601
+
1602
+ if (!idleResult.isIdle && idleResult.confidence < idleThreshold) {
1603
+ // Not idle yet, retry later (urgent messages retry faster)
1604
+ const baseRetryMs = this.config.injectRetryMs ?? 300;
1605
+ const retryMs = priority <= 1 ? Math.floor(baseRetryMs / 2) : baseRetryMs;
1590
1606
  setTimeout(() => this.checkForInjectionOpportunity(), retryMs);
1591
1607
  return;
1592
1608
  }
@@ -1622,15 +1638,19 @@ export class TmuxWrapper extends BaseWrapper {
1622
1638
  }
1623
1639
 
1624
1640
  // Ensure pane output is stable to avoid interleaving with active generation
1641
+ // Pass message priority for adaptive timeout (urgent messages wait less)
1642
+ const msgPriority = getPriorityFromImportance(msg.importance);
1625
1643
  const stablePane = await this.waitForStablePane(
1626
- this.config.outputStabilityTimeoutMs ?? 2000,
1627
- this.config.outputStabilityPollMs ?? 200
1644
+ this.config.outputStabilityTimeoutMs ?? 800,
1645
+ this.config.outputStabilityPollMs ?? 150,
1646
+ 2,
1647
+ msgPriority
1628
1648
  );
1629
1649
  if (!stablePane) {
1630
1650
  this.logStderr('Output still active, re-queuing injection');
1631
1651
  this.messageQueue.unshift(msg);
1632
1652
  this.isInjecting = false;
1633
- setTimeout(() => this.checkForInjectionOpportunity(), this.config.injectRetryMs ?? 500);
1653
+ setTimeout(() => this.checkForInjectionOpportunity(), this.config.injectRetryMs ?? 300);
1634
1654
  return;
1635
1655
  }
1636
1656
 
@@ -1943,15 +1963,56 @@ export class TmuxWrapper extends BaseWrapper {
1943
1963
 
1944
1964
  /**
1945
1965
  * Wait for pane output to stabilize before injecting to avoid interleaving with ongoing output.
1966
+ * Uses adaptive timeout based on idle detector confidence for faster injection when safe.
1967
+ *
1968
+ * @param maxWaitMs - Maximum time to wait (default from config)
1969
+ * @param pollIntervalMs - Polling interval (default from config)
1970
+ * @param requiredStablePolls - Consecutive stable polls needed (default 2)
1971
+ * @param priority - Message priority (lower = more urgent, can use shorter timeout)
1946
1972
  */
1947
- private async waitForStablePane(maxWaitMs = 2000, pollIntervalMs = 200, requiredStablePolls = 2): Promise<boolean> {
1973
+ private async waitForStablePane(
1974
+ maxWaitMs = 800,
1975
+ pollIntervalMs = 150,
1976
+ requiredStablePolls = 2,
1977
+ priority?: number
1978
+ ): Promise<boolean> {
1948
1979
  const start = Date.now();
1980
+
1981
+ // Adaptive timeout based on idle confidence and priority
1982
+ // If idle detector shows high confidence (process state), we can be more aggressive
1983
+ const idleResult = this.checkIdleForInjection();
1984
+ const highConfidence = idleResult.confidence >= 0.9;
1985
+
1986
+ // Priority-based timeout adjustment (urgent messages get shorter timeout)
1987
+ let effectiveMaxWait = maxWaitMs;
1988
+ if (priority !== undefined && priority <= 1) {
1989
+ // Urgent/high priority: reduce timeout by 50%
1990
+ effectiveMaxWait = Math.min(maxWaitMs, highConfidence ? 200 : 400);
1991
+ } else if (highConfidence) {
1992
+ // High confidence from process state: reduce timeout by 60%
1993
+ effectiveMaxWait = Math.floor(maxWaitMs * 0.4);
1994
+ }
1995
+
1949
1996
  let lastSig = await this.capturePaneSignature();
1950
1997
  if (!lastSig) return false;
1951
1998
 
1952
1999
  let stableCount = 0;
1953
2000
 
1954
- while (Date.now() - start < maxWaitMs) {
2001
+ // Fast initial check - if already stable, exit quickly
2002
+ await sleep(Math.min(pollIntervalMs, 50));
2003
+ const initialSig = await this.capturePaneSignature();
2004
+ if (initialSig && initialSig === lastSig) {
2005
+ stableCount = 1;
2006
+ // If high confidence and initial check stable, we're good
2007
+ if (highConfidence && stableCount >= 1) {
2008
+ this.logStderr(`waitForStablePane: fast exit (high confidence, ${Date.now() - start}ms)`);
2009
+ return true;
2010
+ }
2011
+ } else if (initialSig) {
2012
+ lastSig = initialSig;
2013
+ }
2014
+
2015
+ while (Date.now() - start < effectiveMaxWait) {
1955
2016
  await sleep(pollIntervalMs);
1956
2017
  const sig = await this.capturePaneSignature();
1957
2018
  if (!sig) continue;
@@ -1959,6 +2020,7 @@ export class TmuxWrapper extends BaseWrapper {
1959
2020
  if (sig === lastSig) {
1960
2021
  stableCount++;
1961
2022
  if (stableCount >= requiredStablePolls) {
2023
+ this.logStderr(`waitForStablePane: stable after ${Date.now() - start}ms`);
1962
2024
  return true;
1963
2025
  }
1964
2026
  } else {
@@ -1967,7 +2029,13 @@ export class TmuxWrapper extends BaseWrapper {
1967
2029
  }
1968
2030
  }
1969
2031
 
1970
- this.logStderr(`waitForStablePane: timed out after ${maxWaitMs}ms`);
2032
+ // Even on timeout, if we had at least 1 stable poll and high confidence, proceed
2033
+ if (stableCount >= 1 && highConfidence) {
2034
+ this.logStderr(`waitForStablePane: proceeding with partial stability (high confidence)`);
2035
+ return true;
2036
+ }
2037
+
2038
+ this.logStderr(`waitForStablePane: timed out after ${Date.now() - start}ms`);
1971
2039
  return false;
1972
2040
  }
1973
2041