agent-relay 1.3.1 → 1.3.3
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/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/README.md +23 -9
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +58 -76
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +297 -30
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +51 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +113 -22
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +57 -113
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -3
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/dashboard-server/server.js +244 -28
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +40 -0
- package/dist/wrapper/base-wrapper.js +60 -6
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +89 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +14 -2
- package/dist/wrapper/pty-wrapper.js +132 -32
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +20 -2
- package/dist/wrapper/tmux-wrapper.js +163 -40
- package/package.json +3 -1
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
- package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UniversalIdleDetector - Detect when an agent is waiting for input
|
|
3
|
+
*
|
|
4
|
+
* Works across all CLI tools (Claude, Codex, Gemini, Aider, etc.) by combining:
|
|
5
|
+
* 1. Process state inspection via /proc/{pid}/stat (Linux, 95% confidence)
|
|
6
|
+
* 2. Output silence analysis (cross-platform, 60-80% confidence)
|
|
7
|
+
* 3. Natural ending detection (heuristic, 60% confidence)
|
|
8
|
+
*
|
|
9
|
+
* The hybrid approach ensures reliable idle detection regardless of CLI type.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
minSilenceMs: 500,
|
|
14
|
+
bufferLimit: 10000,
|
|
15
|
+
confidenceThreshold: 0.7,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Universal idle detector for any CLI-based agent.
|
|
19
|
+
*/
|
|
20
|
+
export class UniversalIdleDetector {
|
|
21
|
+
lastOutputTime = 0;
|
|
22
|
+
outputBuffer = '';
|
|
23
|
+
pid = null;
|
|
24
|
+
config;
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
27
|
+
// Initialize lastOutputTime to now to avoid immediate false positives
|
|
28
|
+
this.lastOutputTime = Date.now();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set the PID of the agent process to monitor.
|
|
32
|
+
* Required for Linux process state inspection.
|
|
33
|
+
*/
|
|
34
|
+
setPid(pid) {
|
|
35
|
+
this.pid = pid;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the current PID being monitored.
|
|
39
|
+
*/
|
|
40
|
+
getPid() {
|
|
41
|
+
return this.pid;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Process output chunk from the agent.
|
|
45
|
+
* Call this for every output received from the agent process.
|
|
46
|
+
*/
|
|
47
|
+
onOutput(chunk) {
|
|
48
|
+
this.lastOutputTime = Date.now();
|
|
49
|
+
this.outputBuffer += chunk;
|
|
50
|
+
// Keep buffer bounded
|
|
51
|
+
if (this.outputBuffer.length > this.config.bufferLimit) {
|
|
52
|
+
this.outputBuffer = this.outputBuffer.slice(-Math.floor(this.config.bufferLimit / 2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if the agent process is blocked on read (waiting for input).
|
|
57
|
+
* This is the most reliable signal - the OS knows when a process is waiting.
|
|
58
|
+
*
|
|
59
|
+
* Linux-only; returns null on other platforms.
|
|
60
|
+
*/
|
|
61
|
+
isProcessWaitingForInput() {
|
|
62
|
+
if (process.platform !== 'linux' || !this.pid) {
|
|
63
|
+
return null; // Can't determine on non-Linux
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
// Check process state from /proc/{pid}/stat
|
|
67
|
+
// State codes: R=running, S=sleeping, D=disk sleep, Z=zombie, T=stopped
|
|
68
|
+
const statPath = `/proc/${this.pid}/stat`;
|
|
69
|
+
const stat = fs.readFileSync(statPath, 'utf-8');
|
|
70
|
+
const fields = stat.split(' ');
|
|
71
|
+
const state = fields[2]; // Third field is state
|
|
72
|
+
// S (sleeping) often means waiting for I/O
|
|
73
|
+
if (state !== 'S') {
|
|
74
|
+
return { waiting: false }; // Running or other state = not waiting
|
|
75
|
+
}
|
|
76
|
+
// More precise: check what the process is blocked on
|
|
77
|
+
const wchanPath = `/proc/${this.pid}/wchan`;
|
|
78
|
+
const wchan = fs.readFileSync(wchanPath, 'utf-8').trim();
|
|
79
|
+
// Common wait channels for terminal input
|
|
80
|
+
const inputWaitChannels = [
|
|
81
|
+
'wait_woken',
|
|
82
|
+
'poll_schedule_timeout',
|
|
83
|
+
'do_select',
|
|
84
|
+
'n_tty_read',
|
|
85
|
+
'unix_stream_read_generic',
|
|
86
|
+
'pipe_read',
|
|
87
|
+
'ep_poll', // epoll wait
|
|
88
|
+
'futex_wait_queue', // sometimes seen
|
|
89
|
+
];
|
|
90
|
+
const isWaiting = inputWaitChannels.some(ch => wchan.includes(ch));
|
|
91
|
+
return { waiting: isWaiting, wchan };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null; // Process may have exited or permission denied
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get milliseconds since last output.
|
|
99
|
+
*/
|
|
100
|
+
getOutputSilenceMs() {
|
|
101
|
+
return Date.now() - this.lastOutputTime;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if the last output ends "naturally" (complete thought vs mid-sentence).
|
|
105
|
+
* Helps distinguish between pauses in output and waiting for input.
|
|
106
|
+
*/
|
|
107
|
+
hasNaturalEnding() {
|
|
108
|
+
const lastChars = this.outputBuffer.slice(-100).trim();
|
|
109
|
+
if (lastChars.length === 0)
|
|
110
|
+
return true;
|
|
111
|
+
// Positive signals: output ended cleanly
|
|
112
|
+
const naturalEndings = [
|
|
113
|
+
/[.!?]\s*$/, // Sentence ended
|
|
114
|
+
/```\s*$/, // Code block closed
|
|
115
|
+
/\n\n$/, // Paragraph break
|
|
116
|
+
/>\s*$/, // Prompt character
|
|
117
|
+
/\$\s*$/, // Shell prompt
|
|
118
|
+
/>>>\s*$/, // Python/Aider prompt
|
|
119
|
+
/❯\s*$/, // Fancy prompts
|
|
120
|
+
/λ\s*$/, // Lambda prompts
|
|
121
|
+
/→\s*$/, // Arrow prompts
|
|
122
|
+
];
|
|
123
|
+
// Negative signals: output mid-thought
|
|
124
|
+
const midThought = [
|
|
125
|
+
/[,;:]\s*$/, // Comma, semicolon = more coming
|
|
126
|
+
/\w$/, // Ended mid-word (no trailing space)
|
|
127
|
+
/[-–—]\s*$/, // Dash = continuation
|
|
128
|
+
/\(\s*$/, // Open paren
|
|
129
|
+
/\[\s*$/, // Open bracket
|
|
130
|
+
/\{\s*$/, // Open brace
|
|
131
|
+
/\\\s*$/, // Line continuation
|
|
132
|
+
];
|
|
133
|
+
for (const pattern of midThought) {
|
|
134
|
+
if (pattern.test(lastChars)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const pattern of naturalEndings) {
|
|
139
|
+
if (pattern.test(lastChars)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Default: assume natural if no negative signals and some silence
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Determine if the agent is idle and ready for input.
|
|
148
|
+
* Combines multiple signals for reliability across all CLI types.
|
|
149
|
+
*/
|
|
150
|
+
checkIdle(options = {}) {
|
|
151
|
+
const minSilence = options.minSilenceMs ?? this.config.minSilenceMs;
|
|
152
|
+
const signals = [];
|
|
153
|
+
// Signal 1: Process state (most reliable on Linux)
|
|
154
|
+
const processState = this.isProcessWaitingForInput();
|
|
155
|
+
if (processState !== null) {
|
|
156
|
+
if (processState.waiting) {
|
|
157
|
+
signals.push({
|
|
158
|
+
source: 'process_state',
|
|
159
|
+
confidence: 0.95, // Very high - OS-level truth
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
details: processState.wchan,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Process is actively running - definitely not idle
|
|
166
|
+
return {
|
|
167
|
+
isIdle: false,
|
|
168
|
+
confidence: 0.95,
|
|
169
|
+
signals: [{
|
|
170
|
+
source: 'process_state',
|
|
171
|
+
confidence: 0.95,
|
|
172
|
+
timestamp: Date.now(),
|
|
173
|
+
details: 'process running',
|
|
174
|
+
}],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// processState === null means we can't determine (non-Linux)
|
|
179
|
+
// Signal 2: Output silence
|
|
180
|
+
const silenceMs = this.getOutputSilenceMs();
|
|
181
|
+
if (silenceMs > minSilence) {
|
|
182
|
+
// Confidence scales with silence duration (up to 0.8)
|
|
183
|
+
// 500ms = 0.13, 1000ms = 0.27, 2000ms = 0.53, 3000ms = 0.8
|
|
184
|
+
const silenceConfidence = Math.min(silenceMs / 3000, 0.8);
|
|
185
|
+
signals.push({
|
|
186
|
+
source: 'output_silence',
|
|
187
|
+
confidence: silenceConfidence,
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
details: `${silenceMs}ms`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// Signal 3: Natural ending (only if some silence)
|
|
193
|
+
if (silenceMs > 200 && this.hasNaturalEnding()) {
|
|
194
|
+
signals.push({
|
|
195
|
+
source: 'natural_ending',
|
|
196
|
+
confidence: 0.6,
|
|
197
|
+
timestamp: Date.now(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// No signals = not idle
|
|
201
|
+
if (signals.length === 0) {
|
|
202
|
+
return { isIdle: false, confidence: 0, signals: [] };
|
|
203
|
+
}
|
|
204
|
+
// Combine signals
|
|
205
|
+
// Use max confidence, boosted if multiple signals agree
|
|
206
|
+
const maxConfidence = Math.max(...signals.map(s => s.confidence));
|
|
207
|
+
const boost = signals.length > 1 ? 0.1 : 0;
|
|
208
|
+
const combinedConfidence = Math.min(maxConfidence + boost, 1.0);
|
|
209
|
+
return {
|
|
210
|
+
isIdle: combinedConfidence >= this.config.confidenceThreshold,
|
|
211
|
+
confidence: combinedConfidence,
|
|
212
|
+
signals,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Wait for idle state with timeout.
|
|
217
|
+
* Returns the idle result when achieved or after timeout.
|
|
218
|
+
*/
|
|
219
|
+
async waitForIdle(timeoutMs = 30000, pollMs = 200) {
|
|
220
|
+
const startTime = Date.now();
|
|
221
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
222
|
+
const result = this.checkIdle();
|
|
223
|
+
if (result.isIdle) {
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
227
|
+
}
|
|
228
|
+
// Timeout - return current state
|
|
229
|
+
return this.checkIdle();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Reset state (call when agent starts new response).
|
|
233
|
+
*/
|
|
234
|
+
reset() {
|
|
235
|
+
this.outputBuffer = '';
|
|
236
|
+
this.lastOutputTime = Date.now();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get time since last output in milliseconds.
|
|
240
|
+
*/
|
|
241
|
+
getTimeSinceLastOutput() {
|
|
242
|
+
return this.getOutputSilenceMs();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get the PID of a process running in a tmux pane.
|
|
247
|
+
* Uses tmux list-panes with format specifier.
|
|
248
|
+
*/
|
|
249
|
+
export async function getTmuxPanePid(tmuxPath, sessionName) {
|
|
250
|
+
const { exec } = await import('node:child_process');
|
|
251
|
+
const { promisify } = await import('node:util');
|
|
252
|
+
const execAsync = promisify(exec);
|
|
253
|
+
try {
|
|
254
|
+
// Get the PID of the command running in the pane
|
|
255
|
+
const { stdout } = await execAsync(`"${tmuxPath}" list-panes -t ${sessionName} -F "#{pane_pid}" 2>/dev/null`);
|
|
256
|
+
const pid = parseInt(stdout.trim(), 10);
|
|
257
|
+
return isNaN(pid) ? null : pid;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create an idle detector configured for the current platform.
|
|
265
|
+
* Logs a warning on non-Linux platforms where process state inspection isn't available.
|
|
266
|
+
*/
|
|
267
|
+
export function createIdleDetector(config = {}, options = {}) {
|
|
268
|
+
const detector = new UniversalIdleDetector(config);
|
|
269
|
+
if (!options.quiet) {
|
|
270
|
+
if (process.platform === 'darwin') {
|
|
271
|
+
console.warn('[idle-detector] macOS: using output analysis only (less reliable)');
|
|
272
|
+
}
|
|
273
|
+
else if (process.platform === 'win32') {
|
|
274
|
+
console.warn('[idle-detector] Windows: using output analysis only (less reliable)');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return detector;
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=idle-detector.js.map
|
package/dist/wrapper/parser.d.ts
CHANGED
|
@@ -104,6 +104,10 @@ export declare class OutputParser {
|
|
|
104
104
|
*
|
|
105
105
|
* IMPORTANT: We strip ANSI codes for pattern matching, but preserve
|
|
106
106
|
* the original line for output to maintain terminal rendering.
|
|
107
|
+
*
|
|
108
|
+
* OPTIMIZATION: Early exit for lines that can't possibly be relay commands.
|
|
109
|
+
* Most lines don't contain relay patterns, so we avoid expensive regex/ANSI
|
|
110
|
+
* stripping for the common case.
|
|
107
111
|
*/
|
|
108
112
|
private processLine;
|
|
109
113
|
/**
|
package/dist/wrapper/parser.js
CHANGED
|
@@ -640,9 +640,23 @@ export class OutputParser {
|
|
|
640
640
|
*
|
|
641
641
|
* IMPORTANT: We strip ANSI codes for pattern matching, but preserve
|
|
642
642
|
* the original line for output to maintain terminal rendering.
|
|
643
|
+
*
|
|
644
|
+
* OPTIMIZATION: Early exit for lines that can't possibly be relay commands.
|
|
645
|
+
* Most lines don't contain relay patterns, so we avoid expensive regex/ANSI
|
|
646
|
+
* stripping for the common case.
|
|
643
647
|
*/
|
|
644
648
|
processLine(line) {
|
|
645
|
-
//
|
|
649
|
+
// FAST PATH: Quick string check before any expensive operations
|
|
650
|
+
// Most lines don't contain relay commands, so early exit is a big win
|
|
651
|
+
// Check for prefix bases (without the colon to handle custom prefixes like '>>')
|
|
652
|
+
const relayBase = this.options.prefix.replace(/:$/, '');
|
|
653
|
+
const thinkingBase = this.options.thinkingPrefix.replace(/:$/, '');
|
|
654
|
+
const hasRelayPattern = line.includes(relayBase) || line.includes(thinkingBase);
|
|
655
|
+
const hasBlockPattern = line.includes('[[') || line.includes('```');
|
|
656
|
+
if (!hasRelayPattern && !hasBlockPattern) {
|
|
657
|
+
return { command: null, output: line };
|
|
658
|
+
}
|
|
659
|
+
// Strip ANSI codes for pattern matching (only when potentially needed)
|
|
646
660
|
const stripped = stripAnsi(line);
|
|
647
661
|
// Handle code fences
|
|
648
662
|
if (CODE_FENCE.test(stripped)) {
|
|
@@ -672,11 +686,13 @@ export class OutputParser {
|
|
|
672
686
|
const [raw, target, threadProject, threadId, body] = relayMatch;
|
|
673
687
|
// Skip instructional/example text (common in system prompts)
|
|
674
688
|
if (isInstructionalText(body)) {
|
|
689
|
+
console.error(`[parser] Filtered inline message to ${target} - instructional text. Body: ${body.substring(0, 100)}`);
|
|
675
690
|
return { command: null, output: line };
|
|
676
691
|
}
|
|
677
692
|
const { to, project } = parseTarget(target);
|
|
678
693
|
// Skip placeholder target names (common in documentation/examples)
|
|
679
694
|
if (isPlaceholderTarget(to)) {
|
|
695
|
+
console.error(`[parser] Filtered inline message - placeholder target: ${to}`);
|
|
680
696
|
return { command: null, output: line };
|
|
681
697
|
}
|
|
682
698
|
return {
|
|
@@ -795,10 +811,12 @@ export class OutputParser {
|
|
|
795
811
|
shouldFilterFencedInline(target, body) {
|
|
796
812
|
// Check for placeholder target names
|
|
797
813
|
if (isPlaceholderTarget(target)) {
|
|
814
|
+
// Silently filter placeholder targets (common in documentation)
|
|
798
815
|
return true;
|
|
799
816
|
}
|
|
800
817
|
// Check for instructional body content
|
|
801
818
|
if (isInstructionalText(body)) {
|
|
819
|
+
// Silently filter instructional text (common in system prompts)
|
|
802
820
|
return true;
|
|
803
821
|
}
|
|
804
822
|
return false;
|
|
@@ -112,6 +112,11 @@ export declare class PtyWrapper extends BaseWrapper {
|
|
|
112
112
|
* Start the agent process
|
|
113
113
|
*/
|
|
114
114
|
start(): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Wait for the agent to be ready for input.
|
|
117
|
+
* Uses idle detection instead of a fixed delay.
|
|
118
|
+
*/
|
|
119
|
+
private waitForAgentReady;
|
|
115
120
|
/**
|
|
116
121
|
* Inject continuity context from previous session.
|
|
117
122
|
* Called after agent ID initialization to restore state from ledger.
|
|
@@ -225,9 +230,15 @@ export declare class PtyWrapper extends BaseWrapper {
|
|
|
225
230
|
protected parseSpawnReleaseCommands(content: string): void;
|
|
226
231
|
/**
|
|
227
232
|
* Execute spawn via API or callback.
|
|
228
|
-
*
|
|
233
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
229
234
|
*/
|
|
230
235
|
protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
238
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
239
|
+
* not from the dashboard's relay client.
|
|
240
|
+
*/
|
|
241
|
+
private waitAndSendTask;
|
|
231
242
|
/**
|
|
232
243
|
* Execute release via API or callback.
|
|
233
244
|
* Overrides BaseWrapper to add PTY-specific logging and API path.
|
|
@@ -240,7 +251,8 @@ export declare class PtyWrapper extends BaseWrapper {
|
|
|
240
251
|
protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
|
|
241
252
|
/**
|
|
242
253
|
* Wait for output to stabilize before injection.
|
|
243
|
-
*
|
|
254
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
255
|
+
* Returns true if agent is idle and ready for input.
|
|
244
256
|
*/
|
|
245
257
|
private waitForOutputStable;
|
|
246
258
|
/**
|