agent-relay 1.3.0 → 1.3.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/.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/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 +19 -1
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +115 -69
- 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 +47 -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 +98 -15
- 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 +55 -111
- 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/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 +36 -0
- package/dist/wrapper/base-wrapper.js +48 -2
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +84 -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 +7 -1
- package/dist/wrapper/pty-wrapper.js +51 -27
- package/dist/wrapper/tmux-wrapper.d.ts +12 -1
- package/dist/wrapper/tmux-wrapper.js +65 -17
- package/package.json +5 -5
- 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/404.html +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_buildManifest.js +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +0 -2
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +0 -9
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/history/page-8c8bed33beb2bf1c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16f3b49e55b1e0ed.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4a5938c18a11a654.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-547dd0ca55ecd0ba.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +0 -18
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-2ee6beb2ae96d210.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +0 -1
- package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +0 -1
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +0 -1
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +0 -45
- package/dist/dashboard/out/alt-logos/logo.svg +0 -38
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +0 -38
- package/dist/dashboard/out/app/onboarding.html +0 -1
- package/dist/dashboard/out/app/onboarding.txt +0 -7
- package/dist/dashboard/out/app.html +0 -1
- package/dist/dashboard/out/app.txt +0 -7
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/connect-repos.html +0 -1
- package/dist/dashboard/out/connect-repos.txt +0 -7
- package/dist/dashboard/out/history.html +0 -1
- package/dist/dashboard/out/history.txt +0 -7
- package/dist/dashboard/out/index.html +0 -1
- package/dist/dashboard/out/index.txt +0 -7
- package/dist/dashboard/out/login.html +0 -6
- package/dist/dashboard/out/login.txt +0 -7
- package/dist/dashboard/out/metrics.html +0 -1
- package/dist/dashboard/out/metrics.txt +0 -7
- package/dist/dashboard/out/pricing.html +0 -13
- package/dist/dashboard/out/pricing.txt +0 -7
- package/dist/dashboard/out/providers/setup/claude.html +0 -1
- package/dist/dashboard/out/providers/setup/claude.txt +0 -8
- package/dist/dashboard/out/providers/setup/codex.html +0 -1
- package/dist/dashboard/out/providers/setup/codex.txt +0 -8
- package/dist/dashboard/out/providers.html +0 -1
- package/dist/dashboard/out/providers.txt +0 -7
- package/dist/dashboard/out/signup.html +0 -6
- package/dist/dashboard/out/signup.txt +0 -7
- package/dist/dashboard-server/metrics.d.ts +0 -105
- package/dist/dashboard-server/metrics.js +0 -193
- package/dist/dashboard-server/needs-attention.d.ts +0 -24
- package/dist/dashboard-server/needs-attention.js +0 -78
- package/dist/dashboard-server/server.d.ts +0 -15
- package/dist/dashboard-server/server.js +0 -3776
- package/dist/dashboard-server/start.d.ts +0 -6
- package/dist/dashboard-server/start.js +0 -13
- package/dist/dashboard-server/user-bridge.d.ts +0 -103
- package/dist/dashboard-server/user-bridge.js +0 -189
|
@@ -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
|
+
console.error(`[parser] Filtered message - placeholder target: ${target}`);
|
|
798
815
|
return true;
|
|
799
816
|
}
|
|
800
817
|
// Check for instructional body content
|
|
801
818
|
if (isInstructionalText(body)) {
|
|
819
|
+
console.error(`[parser] Filtered message to ${target} - instructional text detected. Body preview: ${body.substring(0, 100)}`);
|
|
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.
|
|
@@ -240,7 +245,8 @@ export declare class PtyWrapper extends BaseWrapper {
|
|
|
240
245
|
protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
|
|
241
246
|
/**
|
|
242
247
|
* Wait for output to stabilize before injection.
|
|
243
|
-
*
|
|
248
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
249
|
+
* Returns true if agent is idle and ready for input.
|
|
244
250
|
*/
|
|
245
251
|
private waitForOutputStable;
|
|
246
252
|
/**
|
|
@@ -184,6 +184,11 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
184
184
|
}
|
|
185
185
|
this.running = true;
|
|
186
186
|
this.sessionStartTime = Date.now();
|
|
187
|
+
// Set PID for idle detector (enables process state inspection on Linux)
|
|
188
|
+
if (this.ptyProcess.pid) {
|
|
189
|
+
this.setIdleDetectorPid(this.ptyProcess.pid);
|
|
190
|
+
console.log(`[pty:${this.config.name}] Idle detector initialized with PID ${this.ptyProcess.pid}`);
|
|
191
|
+
}
|
|
187
192
|
// Skip hooks and continuity in interactive mode - user handles all prompts directly
|
|
188
193
|
if (!this.config.interactive) {
|
|
189
194
|
// Dispatch session start hook (handles trajectory initialization)
|
|
@@ -208,9 +213,9 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
208
213
|
this.config.onExit?.(exitCode);
|
|
209
214
|
this.client.destroy();
|
|
210
215
|
});
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
216
|
+
// Wait for agent to be idle before injecting instructions
|
|
217
|
+
// This replaces the fixed 2-second delay with actual readiness detection
|
|
218
|
+
this.waitForAgentReady().then(() => {
|
|
214
219
|
if (!this.config.interactive) {
|
|
215
220
|
this.injectInstructions();
|
|
216
221
|
}
|
|
@@ -219,7 +224,27 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
219
224
|
if (!this.config.interactive) {
|
|
220
225
|
this.processMessageQueue();
|
|
221
226
|
}
|
|
222
|
-
}
|
|
227
|
+
}).catch(err => {
|
|
228
|
+
console.error(`[pty:${this.config.name}] Failed to wait for agent ready:`, err);
|
|
229
|
+
// Fall back to marking ready anyway to avoid blocking forever
|
|
230
|
+
this.readyForMessages = true;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Wait for the agent to be ready for input.
|
|
235
|
+
* Uses idle detection instead of a fixed delay.
|
|
236
|
+
*/
|
|
237
|
+
async waitForAgentReady() {
|
|
238
|
+
// Minimum wait to ensure the CLI process has started
|
|
239
|
+
await sleep(500);
|
|
240
|
+
// Wait for agent to become idle (CLI fully initialized)
|
|
241
|
+
const result = await this.waitForIdleState(10000, 200);
|
|
242
|
+
if (result.isIdle) {
|
|
243
|
+
console.log(`[pty:${this.config.name}] Agent ready (confidence: ${(result.confidence * 100).toFixed(0)}%)`);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
console.warn(`[pty:${this.config.name}] Agent readiness timeout, proceeding anyway`);
|
|
247
|
+
}
|
|
223
248
|
}
|
|
224
249
|
// Note: initializeAgentId() and getAgentId() are inherited from BaseWrapper
|
|
225
250
|
/**
|
|
@@ -322,6 +347,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
322
347
|
handleOutput(data) {
|
|
323
348
|
// Track output timing for stability checks
|
|
324
349
|
this.lastOutputTime = Date.now();
|
|
350
|
+
// Feed output to idle detector for robust idle detection
|
|
351
|
+
this.feedIdleDetectorOutput(data);
|
|
325
352
|
// Append to raw buffer
|
|
326
353
|
this.rawBuffer += data;
|
|
327
354
|
// Write to log file if available
|
|
@@ -610,6 +637,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
610
637
|
}
|
|
611
638
|
// Skip placeholder targets (documentation examples like "AgentName", "Lead", etc.)
|
|
612
639
|
if (isPlaceholderTarget(target)) {
|
|
640
|
+
console.error(`[pty-wrapper] Filtered fenced message - placeholder target: ${target}`);
|
|
613
641
|
continue;
|
|
614
642
|
}
|
|
615
643
|
// Find the closing >>>
|
|
@@ -630,6 +658,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
630
658
|
}
|
|
631
659
|
// Skip placeholder targets after parsing cross-project syntax
|
|
632
660
|
if (isPlaceholderTarget(to)) {
|
|
661
|
+
console.error(`[pty-wrapper] Filtered fenced message - placeholder target after cross-project parse: ${to}`);
|
|
633
662
|
continue;
|
|
634
663
|
}
|
|
635
664
|
this.sendRelayCommand({
|
|
@@ -676,8 +705,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
676
705
|
if (!body)
|
|
677
706
|
continue;
|
|
678
707
|
// Skip placeholder targets (documentation examples)
|
|
679
|
-
if (isPlaceholderTarget(target))
|
|
708
|
+
if (isPlaceholderTarget(target)) {
|
|
709
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target: ${target}`);
|
|
680
710
|
continue;
|
|
711
|
+
}
|
|
681
712
|
// Parse target for cross-project syntax
|
|
682
713
|
const colonIdx = target.indexOf(':');
|
|
683
714
|
let to = target;
|
|
@@ -687,8 +718,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
687
718
|
to = target.substring(colonIdx + 1);
|
|
688
719
|
}
|
|
689
720
|
// Skip placeholder targets after parsing cross-project syntax
|
|
690
|
-
if (isPlaceholderTarget(to))
|
|
721
|
+
if (isPlaceholderTarget(to)) {
|
|
722
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target after cross-project parse: ${to}`);
|
|
691
723
|
continue;
|
|
724
|
+
}
|
|
692
725
|
this.sendRelayCommand({
|
|
693
726
|
to,
|
|
694
727
|
kind: 'message',
|
|
@@ -702,8 +735,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
702
735
|
if (!body)
|
|
703
736
|
continue;
|
|
704
737
|
// Skip placeholder targets (documentation examples)
|
|
705
|
-
if (isPlaceholderTarget(target))
|
|
738
|
+
if (isPlaceholderTarget(target)) {
|
|
739
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target: ${target}`);
|
|
706
740
|
continue;
|
|
741
|
+
}
|
|
707
742
|
// Parse target for cross-project syntax
|
|
708
743
|
const colonIdx = target.indexOf(':');
|
|
709
744
|
let to = target;
|
|
@@ -713,8 +748,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
713
748
|
to = target.substring(colonIdx + 1);
|
|
714
749
|
}
|
|
715
750
|
// Skip placeholder targets after parsing cross-project syntax
|
|
716
|
-
if (isPlaceholderTarget(to))
|
|
751
|
+
if (isPlaceholderTarget(to)) {
|
|
752
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target after cross-project parse: ${to}`);
|
|
717
753
|
continue;
|
|
754
|
+
}
|
|
718
755
|
this.sendRelayCommand({
|
|
719
756
|
to,
|
|
720
757
|
kind: 'message',
|
|
@@ -1032,27 +1069,14 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1032
1069
|
}
|
|
1033
1070
|
/**
|
|
1034
1071
|
* Wait for output to stabilize before injection.
|
|
1035
|
-
*
|
|
1072
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
1073
|
+
* Returns true if agent is idle and ready for input.
|
|
1036
1074
|
*/
|
|
1037
1075
|
async waitForOutputStable() {
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
await sleep(INJECTION_CONSTANTS.STABILITY_POLL_MS);
|
|
1043
|
-
const timeSinceOutput = Date.now() - this.lastOutputTime;
|
|
1044
|
-
const bufferUnchanged = this.rawBuffer.length === lastBufferLength;
|
|
1045
|
-
// Consider stable if no output for at least one poll interval
|
|
1046
|
-
if (timeSinceOutput >= INJECTION_CONSTANTS.STABILITY_POLL_MS && bufferUnchanged) {
|
|
1047
|
-
stablePolls++;
|
|
1048
|
-
if (stablePolls >= INJECTION_CONSTANTS.REQUIRED_STABLE_POLLS) {
|
|
1049
|
-
return true;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
stablePolls = 0;
|
|
1054
|
-
lastBufferLength = this.rawBuffer.length;
|
|
1055
|
-
}
|
|
1076
|
+
const result = await this.waitForIdleState(INJECTION_CONSTANTS.STABILITY_TIMEOUT_MS, INJECTION_CONSTANTS.STABILITY_POLL_MS);
|
|
1077
|
+
if (result.isIdle) {
|
|
1078
|
+
console.log(`[pty:${this.config.name}] Idle detected (confidence: ${(result.confidence * 100).toFixed(0)}%)`);
|
|
1079
|
+
return true;
|
|
1056
1080
|
}
|
|
1057
1081
|
// Timeout - return true anyway to avoid blocking forever
|
|
1058
1082
|
console.warn(`[pty:${this.config.name}] Stability timeout, proceeding with injection`);
|
|
@@ -127,6 +127,16 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
127
127
|
* Initialize agent ID for continuity/resume functionality (uses logStderr for tmux)
|
|
128
128
|
*/
|
|
129
129
|
protected initializeAgentId(): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Initialize the idle detector with the tmux pane PID.
|
|
132
|
+
* This enables process state inspection on Linux for more reliable idle detection.
|
|
133
|
+
*/
|
|
134
|
+
private initializeIdleDetectorPid;
|
|
135
|
+
/**
|
|
136
|
+
* Wait for the agent to be ready for input.
|
|
137
|
+
* Uses idle detection instead of a fixed delay.
|
|
138
|
+
*/
|
|
139
|
+
private waitForAgentReady;
|
|
130
140
|
/**
|
|
131
141
|
* Inject usage instructions for the agent including persistence protocol
|
|
132
142
|
*/
|
|
@@ -246,7 +256,8 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
246
256
|
*/
|
|
247
257
|
protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
|
|
248
258
|
/**
|
|
249
|
-
* Check if we should inject a message
|
|
259
|
+
* Check if we should inject a message.
|
|
260
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
250
261
|
*/
|
|
251
262
|
private checkForInjectionOpportunity;
|
|
252
263
|
/**
|